Add search file support in the GtkFileChooser. Original patch by Federico
authorEmmanuele Bassi <ebassi@gnome.org>
Wed, 2 May 2007 22:51:43 +0000 (22:51 +0000)
committerEmmanuele Bassi <ebassi@src.gnome.org>
Wed, 2 May 2007 22:51:43 +0000 (22:51 +0000)
2007-05-02  Emmanuele Bassi  <ebassi@gnome.org>

Add search file support in the GtkFileChooser. Original patch
by Federico Mena Quintero; patch updated by Matthias Clasen.
See bug #344785.

* gtk/gtksearchengine.[ch]: Private search engine abstraction
object.

* gtk/gtksearchenginebeagle.[ch]: Private search engine
implementation using libbeagle (via g_module_open()).

* gtk/gtksearchenginesimple.[ch]: Private search engine
implementation using file tree walking.

* gtk/gtksearchenginetracker.[ch]: Private earch engine
implementation using libtracker (via g_module_open()).

* gtk/gtkquery.[ch]: Private query object for the search
engines.

* gtk/gtkfilechooserprivate.h:
* gtk/gtkfilechooserdefault.c: Use the GtkSearchEngine to
query a search engine backend using GtkQuery; create a new
operating mode, OPERATION_MODE_SEARCH, and call the common
operating mode OPERATION_MODE_BROWSE; add support for virtual
shortcuts inside the shortcuts model and create a new "Search"
virtual shortcut.

* gtk/Makefile.am: Update the build with the new files

svn path=/trunk/; revision=17783

14 files changed:
ChangeLog
gtk/Makefile.am
gtk/gtkfilechooserdefault.c
gtk/gtkfilechooserprivate.h
gtk/gtkquery.c [new file with mode: 0644]
gtk/gtkquery.h [new file with mode: 0644]
gtk/gtksearchengine.c [new file with mode: 0644]
gtk/gtksearchengine.h [new file with mode: 0644]
gtk/gtksearchenginebeagle.c [new file with mode: 0644]
gtk/gtksearchenginebeagle.h [new file with mode: 0644]
gtk/gtksearchenginesimple.c [new file with mode: 0644]
gtk/gtksearchenginesimple.h [new file with mode: 0644]
gtk/gtksearchenginetracker.c [new file with mode: 0644]
gtk/gtksearchenginetracker.h [new file with mode: 0644]

index 80fc6cbfdbc030acaa90374f87169d5868d24c37..10a7f098cb9611a0201206c8fecc452d513da683 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,34 @@
+2007-05-02  Emmanuele Bassi  <ebassi@gnome.org>
+
+       Add search file support in the GtkFileChooser. Original patch
+       by Federico Mena Quintero; patch updated by Matthias Clasen.
+       See bug #344785.
+
+       * gtk/gtksearchengine.[ch]: Private search engine abstraction
+       object.
+
+       * gtk/gtksearchenginebeagle.[ch]: Private search engine
+       implementation using libbeagle (via g_module_open()).
+
+       * gtk/gtksearchenginesimple.[ch]: Private search engine
+       implementation using file tree walking.
+
+       * gtk/gtksearchenginetracker.[ch]: Private earch engine
+       implementation using libtracker (via g_module_open()).
+
+       * gtk/gtkquery.[ch]: Private query object for the search
+       engines.
+
+       * gtk/gtkfilechooserprivate.h:
+       * gtk/gtkfilechooserdefault.c: Use the GtkSearchEngine to
+       query a search engine backend using GtkQuery; create a new
+       operating mode, OPERATION_MODE_SEARCH, and call the common
+       operating mode OPERATION_MODE_BROWSE; add support for virtual
+       shortcuts inside the shortcuts model and create a new "Search"
+       virtual shortcut.
+
+       * gtk/Makefile.am: Update the build with the new files
+
 2007-05-02  Armin Burgmeier  <armin@openismus.com>
 
        * gtk/gtkcombobox.c: Destroy the menu in dispose instead of
index 4a5b0764e1e2312b27ea7f6e2979c9c09f2688c8..3097d20544a16189a57677f28ae4a27f16d405f9 100644 (file)
@@ -335,6 +335,11 @@ gtk_semi_private_h_sources =    \
 
 # GTK+ header files that don't get installed
 gtk_private_h_sources =                \
+       gtkquery.h              \
+       gtksearchengine.h       \
+       gtksearchenginebeagle.h \
+       gtksearchenginetracker.h\
+       gtksearchenginesimple.h \
        gtkdndcursors.h         \
        gtkentryprivate.h       \
        gtkfilechooserdefault.h \
@@ -376,6 +381,11 @@ gtk_private_h_sources =            \
 
 # GTK+ C sources to build the library from
 gtk_base_c_sources =            \
+       gtkquery.c              \
+       gtksearchengine.c       \
+       gtksearchenginebeagle.c \
+       gtksearchenginetracker.c\
+       gtksearchenginesimple.c \
        fnmatch.c               \
        gtkaboutdialog.c        \
        gtkaccelgroup.c         \
index a4a596f9aa71a1312428fe24cb34a0d3ad3d7691..d54e1d606c9c8a648c9665d4dcd454a47b93bef7 100644 (file)
@@ -78,6 +78,8 @@
 #include <errno.h>
 #include <string.h>
 #include <time.h>
+#include <sys/stat.h>
+#include <sys/types.h>
 
 
 #ifdef HAVE_UNISTD_H
@@ -172,13 +174,20 @@ enum {
   SHORTCUTS_COL_PIXBUF,
   SHORTCUTS_COL_NAME,
   SHORTCUTS_COL_DATA,
-  SHORTCUTS_COL_IS_VOLUME,
+  SHORTCUTS_COL_TYPE,
   SHORTCUTS_COL_REMOVABLE,
   SHORTCUTS_COL_PIXBUF_VISIBLE,
   SHORTCUTS_COL_HANDLE,
   SHORTCUTS_COL_NUM_COLUMNS
 };
 
+typedef enum {
+  SHORTCUT_TYPE_PATH,
+  SHORTCUT_TYPE_VOLUME,
+  SHORTCUT_TYPE_SEPARATOR,
+  SHORTCUT_TYPE_SEARCH
+} ShortcutType;
+
 /* Column numbers for the file list */
 enum {
   FILE_LIST_COL_NAME,
@@ -187,6 +196,16 @@ enum {
   FILE_LIST_COL_NUM_COLUMNS
 };
 
+/* Column numbers for the search model.  
+ * Keep this in sync with search_setup_model() 
+ */
+enum {
+  SEARCH_MODEL_COL_PATH,
+  SEARCH_MODEL_COL_DISPLAY_NAME,
+  SEARCH_MODEL_COL_COLLATION_KEY,
+  SEARCH_MODEL_COL_STAT
+};
+
 /* Identifiers for target types */
 enum {
   GTK_TREE_MODEL_ROW,
@@ -226,9 +245,19 @@ static const GtkTargetEntry file_list_dest_targets[] = {
 static const int num_file_list_dest_targets = (sizeof (file_list_dest_targets)
                                               / sizeof (file_list_dest_targets[0]));
 
+static gboolean
+search_is_possible (GtkFileChooserDefault *impl)
+{
+  if (impl->search_engine == NULL)
+    impl->search_engine = _gtk_search_engine_new ();
+  
+  return impl->search_engine != NULL;
+}
 
 /* Interesting places in the shortcuts bar */
 typedef enum {
+  SHORTCUTS_SEARCH,
+  SHORTCUTS_SEARCH_SEPARATOR,
   SHORTCUTS_HOME,
   SHORTCUTS_DESKTOP,
   SHORTCUTS_VOLUMES,
@@ -423,6 +452,14 @@ static void location_button_toggled_cb (GtkToggleButton *toggle,
                                        GtkFileChooserDefault *impl);
 static void location_switch_to_path_bar (GtkFileChooserDefault *impl);
 
+static void     search_stop_searching        (GtkFileChooserDefault *impl);
+static void     search_clear_model           (GtkFileChooserDefault *impl, 
+                                             gboolean               remove_from_treeview);
+static gboolean search_should_respond        (GtkFileChooserDefault *impl);
+static void     search_switch_to_browse_mode (GtkFileChooserDefault *impl);
+static GSList  *search_get_selected_paths    (GtkFileChooserDefault *impl);
+static void     search_entry_activate_cb     (GtkEntry              *entry, 
+                                             gpointer               data);
 
 \f
 
@@ -432,26 +469,26 @@ typedef struct {
   GtkTreeModelFilter parent;
 
   GtkFileChooserDefault *impl;
-} ShortcutsModelFilter;
+} ShortcutsPaneModelFilter;
 
 typedef struct {
   GtkTreeModelFilterClass parent_class;
-} ShortcutsModelFilterClass;
+} ShortcutsPaneModelFilterClass;
 
-#define SHORTCUTS_MODEL_FILTER_TYPE (_shortcuts_model_filter_get_type ())
-#define SHORTCUTS_MODEL_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHORTCUTS_MODEL_FILTER_TYPE, ShortcutsModelFilter))
+#define SHORTCUTS_PANE_MODEL_FILTER_TYPE (_shortcuts_pane_model_filter_get_type ())
+#define SHORTCUTS_PANE_MODEL_FILTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHORTCUTS_PANE_MODEL_FILTER_TYPE, ShortcutsPaneModelFilter))
 
-static void shortcuts_model_filter_drag_source_iface_init (GtkTreeDragSourceIface *iface);
+static void shortcuts_pane_model_filter_drag_source_iface_init (GtkTreeDragSourceIface *iface);
 
-G_DEFINE_TYPE_WITH_CODE (ShortcutsModelFilter,
-                        _shortcuts_model_filter,
+G_DEFINE_TYPE_WITH_CODE (ShortcutsPaneModelFilter,
+                        _shortcuts_pane_model_filter,
                         GTK_TYPE_TREE_MODEL_FILTER,
                         G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE,
-                                               shortcuts_model_filter_drag_source_iface_init))
+                                               shortcuts_pane_model_filter_drag_source_iface_init))
 
-static GtkTreeModel *shortcuts_model_filter_new (GtkFileChooserDefault *impl,
-                                                GtkTreeModel          *child_model,
-                                                GtkTreePath           *root);
+static GtkTreeModel *shortcuts_pane_model_filter_new (GtkFileChooserDefault *impl,
+                                                     GtkTreeModel          *child_model,
+                                                     GtkTreePath           *root);
 
 
 
@@ -680,6 +717,7 @@ _gtk_file_chooser_default_init (GtkFileChooserDefault *impl)
   impl->reload_state = RELOAD_EMPTY;
   impl->pending_select_paths = NULL;
   impl->location_mode = LOCATION_MODE_PATH_BAR;
+  impl->operation_mode = OPERATION_MODE_BROWSE;
 
   gtk_box_set_spacing (GTK_BOX (impl), 12);
 
@@ -695,22 +733,24 @@ shortcuts_free_row_data (GtkFileChooserDefault *impl,
                         GtkTreeIter           *iter)
 {
   gpointer col_data;
-  gboolean is_volume;
+  ShortcutType shortcut_type;
   GtkFileSystemHandle *handle;
 
   gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), iter,
                      SHORTCUTS_COL_DATA, &col_data,
-                     SHORTCUTS_COL_IS_VOLUME, &is_volume,
+                     SHORTCUTS_COL_TYPE, &shortcut_type,
                      SHORTCUTS_COL_HANDLE, &handle,
                      -1);
 
   if (handle)
     gtk_file_system_cancel_operation (handle);
 
-  if (!col_data)
+  if (!(shortcut_type == SHORTCUT_TYPE_PATH || 
+       shortcut_type == SHORTCUT_TYPE_VOLUME) ||
+      !col_data)
     return;
 
-  if (is_volume)
+  if (shortcut_type == SHORTCUT_TYPE_VOLUME)
     {
       GtkFileSystemVolume *volume;
 
@@ -721,6 +761,8 @@ shortcuts_free_row_data (GtkFileChooserDefault *impl,
     {
       GtkFilePath *path;
 
+      g_assert (shortcut_type == SHORTCUT_TYPE_PATH);
+
       path = col_data;
       gtk_file_path_free (path);
     }
@@ -807,8 +849,11 @@ gtk_file_chooser_default_finalize (GObject *object)
   GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (object);
   GSList *l;
 
-  if (impl->shortcuts_filter_model)
-    g_object_unref (impl->shortcuts_filter_model);
+  if (impl->shortcuts_pane_filter_model)
+    g_object_unref (impl->shortcuts_pane_filter_model);
+
+  if (impl->shortcuts_combo_filter_model)
+    g_object_unref (impl->shortcuts_combo_filter_model);
 
   shortcuts_free (impl);
 
@@ -846,6 +891,8 @@ gtk_file_chooser_default_finalize (GObject *object)
   if (impl->sort_model)
     g_object_unref (impl->sort_model);
 
+  search_clear_model (impl, FALSE);
+
   g_free (impl->preview_display_name);
 
   g_free (impl->edited_new_text);
@@ -1098,6 +1145,14 @@ set_preview_widget (GtkFileChooserDefault *impl,
   update_preview_widget_visibility (impl);
 }
 
+/* Renders a "Search" icon at an appropriate size for a tree view */
+static GdkPixbuf *
+render_search_icon (GtkFileChooserDefault *impl)
+{
+  return gtk_widget_render_icon (GTK_WIDGET (impl), GTK_STOCK_FIND, GTK_ICON_SIZE_SMALL_TOOLBAR, NULL);
+}
+
+
 /* Re-reads all the icons for the shortcuts, used when the theme changes */
 struct ReloadIconsData
 {
@@ -1168,19 +1223,20 @@ shortcuts_reload_icons (GtkFileChooserDefault *impl)
   do
     {
       gpointer data;
-      gboolean is_volume;
+      ShortcutType shortcut_type;
       gboolean pixbuf_visible;
       GdkPixbuf *pixbuf;
 
       gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter,
                          SHORTCUTS_COL_DATA, &data,
-                         SHORTCUTS_COL_IS_VOLUME, &is_volume,
+                         SHORTCUTS_COL_TYPE, &shortcut_type,
                          SHORTCUTS_COL_PIXBUF_VISIBLE, &pixbuf_visible,
                          -1);
 
-      if (pixbuf_visible && data)
+      pixbuf = NULL;
+      if (pixbuf_visible)
         {
-         if (is_volume)
+         if (shortcut_type == SHORTCUT_TYPE_VOLUME)
            {
              GtkFileSystemVolume *volume;
 
@@ -1195,46 +1251,53 @@ shortcuts_reload_icons (GtkFileChooserDefault *impl)
              if (pixbuf)
                g_object_unref (pixbuf);
            }
-         else if (gtk_file_system_path_is_local (impl->file_system, (GtkFilePath *)data))
-           {
-             const GtkFilePath *path;
-             struct ReloadIconsData *info;
-             GtkTreePath *tree_path;
-             GtkFileSystemHandle *handle;
-
-             path = data;
-
-             info = g_new0 (struct ReloadIconsData, 1);
-             info->impl = g_object_ref (impl);
-             tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &iter);
-             info->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (impl->shortcuts_model), tree_path);
-             gtk_tree_path_free (tree_path);
-
-             handle = gtk_file_system_get_info (impl->file_system, path,
-                                                GTK_FILE_INFO_ICON,
-                                                shortcuts_reload_icons_get_info_cb,
-                                                info);
-             impl->reload_icon_handles = g_slist_append (impl->reload_icon_handles, handle);
-           }
-         else
+         else if (shortcut_type == SHORTCUT_TYPE_PATH)
+            {
+             if (gtk_file_system_path_is_local (impl->file_system, (GtkFilePath *)data))
+               {
+                 const GtkFilePath *path;
+                 struct ReloadIconsData *info;
+                 GtkTreePath *tree_path;
+                 GtkFileSystemHandle *handle;
+
+                 path = data;
+
+                 info = g_new0 (struct ReloadIconsData, 1);
+                 info->impl = g_object_ref (impl);
+                 tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &iter);
+                 info->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (impl->shortcuts_model), tree_path);
+                 gtk_tree_path_free (tree_path);
+
+                 handle = gtk_file_system_get_info (impl->file_system, path,
+                                                    GTK_FILE_INFO_ICON,
+                                                    shortcuts_reload_icons_get_info_cb,
+                                                    info);
+                 impl->reload_icon_handles = g_slist_append (impl->reload_icon_handles, handle);
+               }
+              else
+               {
+                 GtkIconTheme *icon_theme;
+
+                 /* Don't call get_info for remote paths to avoid latency and
+                  * auth dialogs.
+                  * If we switch to a better bookmarks file format (XBEL), we
+                  * should use mime info to get a better icon.
+                  */
+                 icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl)));
+                 pixbuf = gtk_icon_theme_load_icon (icon_theme, "gnome-fs-directory", 
+                                                    impl->icon_size, 0, NULL);
+
+                 gtk_list_store_set (impl->shortcuts_model, &iter,
+                                     SHORTCUTS_COL_PIXBUF, pixbuf,
+                                     -1);
+
+                 if (pixbuf)
+                   g_object_unref (pixbuf);
+               }
+            }
+         else if (shortcut_type == SHORTCUT_TYPE_SEARCH)
            {
-             GtkIconTheme *icon_theme;
-
-             /* Don't call get_info for remote paths to avoid latency and
-              * auth dialogs.
-              * If we switch to a better bookmarks file format (XBEL), we
-              * should use mime info to get a better icon.
-              */
-             icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl)));
-             pixbuf = gtk_icon_theme_load_icon (icon_theme, "gnome-fs-directory", 
-                                                impl->icon_size, 0, NULL);
-
-             gtk_list_store_set (impl->shortcuts_model, &iter,
-                                 SHORTCUTS_COL_PIXBUF, pixbuf,
-                                 -1);
-
-             if (pixbuf)
-               g_object_unref (pixbuf);
+             pixbuf = render_search_icon (impl);
            }
        }
     }
@@ -1366,7 +1429,6 @@ get_file_info_finished (GtkFileSystemHandle *handle,
 {
   gint pos = -1;
   gboolean cancelled = handle->cancelled;
-  gboolean is_volume = FALSE;
   GdkPixbuf *pixbuf;
   GtkTreePath *path;
   GtkTreeIter iter;
@@ -1431,12 +1493,15 @@ get_file_info_finished (GtkFileSystemHandle *handle,
                      SHORTCUTS_COL_PIXBUF, pixbuf,
                      SHORTCUTS_COL_PIXBUF_VISIBLE, TRUE,
                      SHORTCUTS_COL_NAME, request->label_copy,
-                     SHORTCUTS_COL_IS_VOLUME, is_volume,
+                     SHORTCUTS_COL_TYPE, SHORTCUT_TYPE_PATH,
                      SHORTCUTS_COL_REMOVABLE, request->removable,
                      -1);
 
-  if (request->impl->shortcuts_filter_model)
-    gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (request->impl->shortcuts_filter_model));
+  if (request->impl->shortcuts_pane_filter_model)
+    gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (request->impl->shortcuts_pane_filter_model));
+
+  if (request->impl->shortcuts_combo_filter_model)
+    gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (request->impl->shortcuts_combo_filter_model));
 
   if (request->type == SHORTCUTS_CURRENT_FOLDER
       && request->impl->save_folder_combo != NULL)
@@ -1448,7 +1513,7 @@ get_file_info_finished (GtkFileSystemHandle *handle,
       g_signal_handlers_block_by_func (request->impl->save_folder_combo,
                                       G_CALLBACK (save_folder_combo_changed_cb),
                                       request->impl);
-      gtk_combo_box_set_active (GTK_COMBO_BOX (request->impl->save_folder_combo), pos);
+      gtk_combo_box_set_active (GTK_COMBO_BOX (request->impl->save_folder_combo), request->impl->has_search ? pos - 2 : pos);
       g_signal_handlers_unblock_by_func (request->impl->save_folder_combo,
                                         G_CALLBACK (save_folder_combo_changed_cb),
                                         request->impl);
@@ -1522,7 +1587,7 @@ _gtk_file_chooser_label_for_uri (const gchar *uri)
 static void
 shortcuts_insert_path (GtkFileChooserDefault *impl,
                       int                    pos,
-                      gboolean               is_volume,
+                      ShortcutType           shortcut_type,
                       GtkFileSystemVolume   *volume,
                       const GtkFilePath     *path,
                       const char            *label,
@@ -1535,79 +1600,89 @@ shortcuts_insert_path (GtkFileChooserDefault *impl,
   GtkTreeIter iter;
   GtkIconTheme *icon_theme;      
 
-  profile_start ("start", is_volume ? "volume" : (char *) path);
+  profile_start ("start", (shortcut_type == SHORTCUT_TYPE_VOLUME) ? "volume" 
+                         : ((shortcut_type == SHORTCUT_TYPE_PATH) ? (char *) path : NULL));
 
-  if (is_volume)
+  if (shortcut_type == SHORTCUT_TYPE_VOLUME)
     {
       data = volume;
       label_copy = gtk_file_system_volume_get_display_name (impl->file_system, volume);
       pixbuf = gtk_file_system_volume_render_icon (impl->file_system, volume, GTK_WIDGET (impl),
                                                   impl->icon_size, NULL);
     }
-  else if (gtk_file_system_path_is_local (impl->file_system, path))
+  else if (shortcut_type == SHORTCUT_TYPE_PATH)
     {
-      struct ShortcutsInsertRequest *request;
-      GtkFileSystemHandle *handle;
-      GtkTreePath *p;
-
-      request = g_new0 (struct ShortcutsInsertRequest, 1);
-      request->impl = g_object_ref (impl);
-      request->path = gtk_file_path_copy (path);
-      request->name_only = TRUE;
-      request->removable = removable;
-      request->pos = pos;
-      request->type = type;
-      if (label)
-       request->label_copy = g_strdup (label);
-
-      if (pos == -1)
-       gtk_list_store_append (impl->shortcuts_model, &iter);
+      if (gtk_file_system_path_is_local (impl->file_system, path))
+        {
+          struct ShortcutsInsertRequest *request;
+          GtkFileSystemHandle *handle;
+          GtkTreePath *p;
+
+          request = g_new0 (struct ShortcutsInsertRequest, 1);
+          request->impl = g_object_ref (impl);
+          request->path = gtk_file_path_copy (path);
+          request->name_only = TRUE;
+          request->removable = removable;
+          request->pos = pos;
+          request->type = type;
+          if (label)
+           request->label_copy = g_strdup (label);
+
+          if (pos == -1)
+           gtk_list_store_append (impl->shortcuts_model, &iter);
+          else
+           gtk_list_store_insert (impl->shortcuts_model, &iter, pos);
+
+          p = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &iter);
+          request->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (impl->shortcuts_model), p);
+          gtk_tree_path_free (p);
+
+          handle = gtk_file_system_get_info (request->impl->file_system, request->path,
+                                            GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_HIDDEN | GTK_FILE_INFO_ICON,
+                                            get_file_info_finished, request);
+
+          gtk_list_store_set (impl->shortcuts_model, &iter,
+                             SHORTCUTS_COL_DATA, gtk_file_path_copy (path),
+                             SHORTCUTS_COL_TYPE, SHORTCUT_TYPE_PATH,
+                             SHORTCUTS_COL_HANDLE, handle,
+                             -1);
+
+          shortcuts_update_count (impl, type, 1);
+
+          return;
+        }
       else
-       gtk_list_store_insert (impl->shortcuts_model, &iter, pos);
-
-      p = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), &iter);
-      request->row_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL (impl->shortcuts_model), p);
-      gtk_tree_path_free (p);
-
-      handle = gtk_file_system_get_info (request->impl->file_system, request->path,
-                                        GTK_FILE_INFO_DISPLAY_NAME | GTK_FILE_INFO_IS_HIDDEN | GTK_FILE_INFO_ICON,
-                                        get_file_info_finished, request);
+        {
+          /* Don't call get_info for remote paths to avoid latency and
+           * auth dialogs.
+           */
+          data = gtk_file_path_copy (path);
+          if (label)
+           label_copy = g_strdup (label);
+          else
+           {
+             gchar *uri;
 
-      gtk_list_store_set (impl->shortcuts_model, &iter,
-                         SHORTCUTS_COL_DATA, gtk_file_path_copy (path),
-                         SHORTCUTS_COL_IS_VOLUME, is_volume,
-                         SHORTCUTS_COL_HANDLE, handle,
-                         -1);
+             uri = gtk_file_system_path_to_uri (impl->file_system, path);
 
-      shortcuts_update_count (impl, type, 1);
+             label_copy = _gtk_file_chooser_label_for_uri (uri);
 
-      return;
+             g_free (uri);
+           }
+    
+          /* If we switch to a better bookmarks file format (XBEL), we
+           * should use mime info to get a better icon.
+           */
+          icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl)));
+          pixbuf = gtk_icon_theme_load_icon (icon_theme, "gnome-fs-directory", 
+                                            impl->icon_size, 0, NULL);
+        }
     }
-  else
+   else
     {
-      /* Don't call get_info for remote paths to avoid latency and
-       * auth dialogs.
-       */
-      data = gtk_file_path_copy (path);
-      if (label)
-       label_copy = g_strdup (label);
-      else
-       {
-         gchar *uri;
-
-         uri = gtk_file_system_path_to_uri (impl->file_system, path);
-
-         label_copy = _gtk_file_chooser_label_for_uri (uri);
+      g_assert_not_reached ();
 
-         g_free (uri);
-       }
-    
-      /* If we switch to a better bookmarks file format (XBEL), we
-       * should use mime info to get a better icon.
-       */
-      icon_theme = gtk_icon_theme_get_for_screen (gtk_widget_get_screen (GTK_WIDGET (impl)));
-      pixbuf = gtk_icon_theme_load_icon (icon_theme, "gnome-fs-directory", 
-                                        impl->icon_size, 0, NULL);
+      return;
     }
 
   if (pos == -1)
@@ -1622,13 +1697,16 @@ shortcuts_insert_path (GtkFileChooserDefault *impl,
                      SHORTCUTS_COL_PIXBUF_VISIBLE, TRUE,
                      SHORTCUTS_COL_NAME, label_copy,
                      SHORTCUTS_COL_DATA, data,
-                     SHORTCUTS_COL_IS_VOLUME, is_volume,
+                     SHORTCUTS_COL_TYPE, shortcut_type,
                      SHORTCUTS_COL_REMOVABLE, removable,
                      SHORTCUTS_COL_HANDLE, NULL,
                      -1);
 
-  if (impl->shortcuts_filter_model)
-    gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model));
+  if (impl->shortcuts_pane_filter_model)
+    gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model));
+
+  if (impl->shortcuts_combo_filter_model)
+    gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_combo_filter_model));
 
   if (type == SHORTCUTS_CURRENT_FOLDER && impl->save_folder_combo != NULL)
     {
@@ -1640,7 +1718,8 @@ shortcuts_insert_path (GtkFileChooserDefault *impl,
       g_signal_handlers_block_by_func (impl->save_folder_combo,
                                       G_CALLBACK (save_folder_combo_changed_cb),
                                       impl);
-      gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo), combo_pos);
+      gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo), 
+                               impl->has_search ? combo_pos - 2 : combo_pos);
       g_signal_handlers_unblock_by_func (impl->save_folder_combo,
                                         G_CALLBACK (save_folder_combo_changed_cb),
                                         impl);
@@ -1654,6 +1733,30 @@ shortcuts_insert_path (GtkFileChooserDefault *impl,
   profile_end ("end", NULL);
 }
 
+static void
+shortcuts_append_search (GtkFileChooserDefault *impl)
+{
+  GdkPixbuf *pixbuf;
+  GtkTreeIter iter;
+
+  pixbuf = render_search_icon (impl);
+
+  gtk_list_store_append (impl->shortcuts_model, &iter);
+  gtk_list_store_set (impl->shortcuts_model, &iter,
+                     SHORTCUTS_COL_PIXBUF, pixbuf,
+                     SHORTCUTS_COL_PIXBUF_VISIBLE, TRUE,
+                     SHORTCUTS_COL_NAME, _("Search"),
+                     SHORTCUTS_COL_DATA, NULL,
+                     SHORTCUTS_COL_TYPE, SHORTCUT_TYPE_SEARCH,
+                     SHORTCUTS_COL_REMOVABLE, FALSE,
+                     -1);
+
+  if (pixbuf)
+    g_object_unref (pixbuf);
+
+  impl->has_search = TRUE;
+}
+
 /* Appends an item for the user's home directory to the shortcuts model */
 static void
 shortcuts_append_home (GtkFileChooserDefault *impl)
@@ -1672,7 +1775,8 @@ shortcuts_append_home (GtkFileChooserDefault *impl)
 
   home_path = gtk_file_system_filename_to_path (impl->file_system, home);
 
-  shortcuts_insert_path (impl, -1, FALSE, NULL, home_path, NULL, FALSE, SHORTCUTS_HOME);
+  shortcuts_insert_path (impl, -1, SHORTCUT_TYPE_PATH, NULL, home_path, NULL, FALSE, SHORTCUTS_HOME);
+  impl->has_home = TRUE;
 
   gtk_file_path_free (home_path);
 
@@ -1705,7 +1809,9 @@ shortcuts_append_desktop (GtkFileChooserDefault *impl)
   path = gtk_file_system_filename_to_path (impl->file_system, name);
   g_free (name);
 
-  shortcuts_insert_path (impl, -1, FALSE, NULL, path, _("Desktop"), FALSE, SHORTCUTS_DESKTOP);
+  shortcuts_insert_path (impl, -1, SHORTCUT_TYPE_PATH, NULL, path, _("Desktop"), FALSE, SHORTCUTS_DESKTOP);
+  impl->has_desktop = TRUE;
+
   /* We do not actually pop up an error dialog if there is no desktop directory
    * because some people may really not want to have one.
    */
@@ -1744,7 +1850,7 @@ shortcuts_append_paths (GtkFileChooserDefault *impl,
       label = gtk_file_system_get_bookmark_label (impl->file_system, path);
 
       /* NULL GError, but we don't really want to show error boxes here */
-      shortcuts_insert_path (impl, start_row + num_inserted, FALSE, NULL, path, label, TRUE, SHORTCUTS_BOOKMARKS);
+      shortcuts_insert_path (impl, start_row + num_inserted, SHORTCUT_TYPE_PATH, NULL, path, label, TRUE, SHORTCUTS_BOOKMARKS);
       num_inserted++;
 
       g_free (label);
@@ -1763,6 +1869,16 @@ shortcuts_get_index (GtkFileChooserDefault *impl,
   int n;
 
   n = 0;
+  
+  if (where == SHORTCUTS_SEARCH)
+    goto out;
+
+  n += impl->has_search ? 1 : 0;
+
+  if (where == SHORTCUTS_SEARCH_SEPARATOR)
+    goto out;
+
+  n += impl->has_search ? 1 : 0;
 
   if (where == SHORTCUTS_HOME)
     goto out;
@@ -1860,15 +1976,18 @@ shortcuts_add_volumes (GtkFileChooserDefault *impl)
            }
        }
 
-      shortcuts_insert_path (impl, start_row + n, TRUE, volume, NULL, NULL, FALSE, SHORTCUTS_VOLUMES);
+      shortcuts_insert_path (impl, start_row + n, SHORTCUT_TYPE_VOLUME, volume, NULL, NULL, FALSE, SHORTCUTS_VOLUMES);
       n++;
     }
 
   impl->num_volumes = n;
   g_slist_free (list);
 
-  if (impl->shortcuts_filter_model)
-    gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model));
+  if (impl->shortcuts_pane_filter_model)
+    gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model));
+
+  if (impl->shortcuts_combo_filter_model)
+    gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_combo_filter_model));
 
   impl->changing_folder = old_changing_folders;
 
@@ -1878,11 +1997,13 @@ shortcuts_add_volumes (GtkFileChooserDefault *impl)
 /* Inserts a separator node in the shortcuts list */
 static void
 shortcuts_insert_separator (GtkFileChooserDefault *impl,
-                           ShortcutsIndex where)
+                           ShortcutsIndex         where)
 {
   GtkTreeIter iter;
 
-  g_assert (where == SHORTCUTS_BOOKMARKS_SEPARATOR || where == SHORTCUTS_CURRENT_FOLDER_SEPARATOR);
+  g_assert (where == SHORTCUTS_SEARCH_SEPARATOR || 
+           where == SHORTCUTS_BOOKMARKS_SEPARATOR || 
+           where == SHORTCUTS_CURRENT_FOLDER_SEPARATOR);
 
   gtk_list_store_insert (impl->shortcuts_model, &iter,
                         shortcuts_get_index (impl, where));
@@ -1891,6 +2012,7 @@ shortcuts_insert_separator (GtkFileChooserDefault *impl,
                      SHORTCUTS_COL_PIXBUF_VISIBLE, FALSE,
                      SHORTCUTS_COL_NAME, NULL,
                      SHORTCUTS_COL_DATA, NULL,
+                     SHORTCUTS_COL_TYPE, SHORTCUT_TYPE_SEPARATOR,
                      -1);
 }
 
@@ -1903,7 +2025,7 @@ shortcuts_add_bookmarks (GtkFileChooserDefault *impl)
   GtkTreeIter iter;
   GtkFilePath *list_selected = NULL;
   GtkFilePath *combo_selected = NULL;
-  gboolean is_volume;
+  ShortcutType shortcut_type;
   gpointer col_data;
 
   profile_start ("start", NULL);
@@ -1916,10 +2038,10 @@ shortcuts_add_bookmarks (GtkFileChooserDefault *impl)
       gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), 
                          &iter, 
                          SHORTCUTS_COL_DATA, &col_data,
-                         SHORTCUTS_COL_IS_VOLUME, &is_volume,
+                         SHORTCUTS_COL_TYPE, &shortcut_type,
                          -1);
 
-      if (col_data && !is_volume)
+      if (col_data && shortcut_type == SHORTCUT_TYPE_PATH)
        list_selected = gtk_file_path_copy (col_data);
     }
 
@@ -1927,13 +2049,18 @@ shortcuts_add_bookmarks (GtkFileChooserDefault *impl)
       gtk_combo_box_get_active_iter (GTK_COMBO_BOX (impl->save_folder_combo), 
                                     &iter))
     {
+      GtkTreeIter child_iter;
+
+      gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER  (impl->shortcuts_combo_filter_model),
+                                                       &child_iter,
+                                                       &iter);
       gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), 
-                         &iter, 
+                         &child_iter, 
                          SHORTCUTS_COL_DATA, &col_data,
-                         SHORTCUTS_COL_IS_VOLUME, &is_volume,
+                         SHORTCUTS_COL_TYPE, &shortcut_type,
                          -1);
       
-      if (col_data && !is_volume)
+      if (col_data && shortcut_type == SHORTCUT_TYPE_PATH)
        combo_selected = gtk_file_path_copy (col_data);
     }
 
@@ -1951,8 +2078,11 @@ shortcuts_add_bookmarks (GtkFileChooserDefault *impl)
   if (impl->num_bookmarks > 0)
     shortcuts_insert_separator (impl, SHORTCUTS_BOOKMARKS_SEPARATOR);
 
-  if (impl->shortcuts_filter_model)
-    gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model));
+  if (impl->shortcuts_pane_filter_model)
+    gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model));
+
+  if (impl->shortcuts_combo_filter_model)
+    gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (impl->shortcuts_combo_filter_model));
 
   if (list_selected)
     {
@@ -1966,8 +2096,8 @@ shortcuts_add_bookmarks (GtkFileChooserDefault *impl)
 
       pos = shortcut_find_position (impl, combo_selected);
       if (pos != -1)
-       gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo), 
-                                 pos);
+       gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo),
+                                 impl->has_search ? pos - 2 : pos);
       gtk_file_path_free (combo_selected);
     }
   
@@ -2012,11 +2142,11 @@ shortcuts_add_current_folder (GtkFileChooserDefault *impl)
       if (base_path &&
          strcmp (gtk_file_path_get_string (base_path), gtk_file_path_get_string (impl->current_folder)) == 0)
        {
-         shortcuts_insert_path (impl, pos, TRUE, volume, NULL, NULL, FALSE, SHORTCUTS_CURRENT_FOLDER);
+         shortcuts_insert_path (impl, pos, SHORTCUT_TYPE_VOLUME, volume, NULL, NULL, FALSE, SHORTCUTS_CURRENT_FOLDER);
        }
       else
         {
-         shortcuts_insert_path (impl, pos, FALSE, NULL, impl->current_folder, NULL, FALSE, SHORTCUTS_CURRENT_FOLDER);
+         shortcuts_insert_path (impl, pos, SHORTCUT_TYPE_PATH, NULL, impl->current_folder, NULL, FALSE, SHORTCUTS_CURRENT_FOLDER);
          if (volume)
            gtk_file_system_volume_free (impl->file_system, volume);
        }
@@ -2025,7 +2155,8 @@ shortcuts_add_current_folder (GtkFileChooserDefault *impl)
        gtk_file_path_free (base_path);
     }
   else if (impl->save_folder_combo != NULL)
-    gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo), pos);
+    gtk_combo_box_set_active (GTK_COMBO_BOX (impl->save_folder_combo), 
+                             impl->has_search ? pos - 2 : pos);
 }
 
 /* Updates the current folder row in the shortcuts model */
@@ -2047,9 +2178,9 @@ shortcuts_update_current_folder (GtkFileChooserDefault *impl)
 
 /* Filter function used for the shortcuts filter model */
 static gboolean
-shortcuts_filter_cb (GtkTreeModel          *model,
-                    GtkTreeIter           *iter,
-                    gpointer               data)
+shortcuts_pane_filter_cb (GtkTreeModel *model,
+                         GtkTreeIter  *iter,
+                         gpointer      data)
 {
   GtkFileChooserDefault *impl;
   GtkTreePath *path;
@@ -2076,11 +2207,17 @@ shortcuts_model_create (GtkFileChooserDefault *impl)
                                              GDK_TYPE_PIXBUF,  /* pixbuf */
                                              G_TYPE_STRING,    /* name */
                                              G_TYPE_POINTER,   /* path or volume */
-                                             G_TYPE_BOOLEAN,   /* is the previous column a volume? */
+                                             G_TYPE_INT,       /* ShortcutType */
                                              G_TYPE_BOOLEAN,   /* removable */
                                              G_TYPE_BOOLEAN,   /* pixbuf cell visibility */
                                              G_TYPE_POINTER);   /* GtkFileSystemHandle */
 
+  if (search_is_possible (impl))
+    {
+      shortcuts_append_search (impl);
+      shortcuts_insert_separator (impl, SHORTCUTS_SEARCH_SEPARATOR);
+    }
+  
   if (impl->file_system)
     {
       shortcuts_append_home (impl);
@@ -2088,12 +2225,12 @@ shortcuts_model_create (GtkFileChooserDefault *impl)
       shortcuts_add_volumes (impl);
     }
 
-  impl->shortcuts_filter_model = shortcuts_model_filter_new (impl,
-                                                            GTK_TREE_MODEL (impl->shortcuts_model),
-                                                            NULL);
+  impl->shortcuts_pane_filter_model = shortcuts_pane_model_filter_new (impl,
+                                                                      GTK_TREE_MODEL (impl->shortcuts_model),
+                                                                      NULL);
 
-  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model),
-                                         shortcuts_filter_cb,
+  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model),
+                                         shortcuts_pane_filter_cb,
                                          impl,
                                          NULL);
 }
@@ -2311,16 +2448,16 @@ shortcut_find_position (GtkFileChooserDefault *impl,
   for (i = 0; i < current_folder_separator_idx; i++)
     {
       gpointer col_data;
-      gboolean is_volume;
+      ShortcutType shortcut_type;
 
       gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter,
                          SHORTCUTS_COL_DATA, &col_data,
-                         SHORTCUTS_COL_IS_VOLUME, &is_volume,
+                         SHORTCUTS_COL_TYPE, &shortcut_type,
                          -1);
 
       if (col_data)
        {
-         if (is_volume)
+         if (shortcut_type == SHORTCUT_TYPE_VOLUME)
            {
              GtkFileSystemVolume *volume;
              GtkFilePath *base_path;
@@ -2336,7 +2473,7 @@ shortcut_find_position (GtkFileChooserDefault *impl,
              if (exists)
                return i;
            }
-         else
+         else if (shortcut_type == SHORTCUT_TYPE_PATH)
            {
              GtkFilePath *model_path;
 
@@ -2442,7 +2579,7 @@ shortcuts_get_selected (GtkFileChooserDefault *impl,
   if (!gtk_tree_selection_get_selected (selection, NULL, &parent_iter))
     return FALSE;
 
-  gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model),
+  gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model),
                                                    iter,
                                                    &parent_iter);
   return TRUE;
@@ -2465,11 +2602,12 @@ remove_selected_bookmarks (GtkFileChooserDefault *impl)
                      SHORTCUTS_COL_DATA, &col_data,
                      SHORTCUTS_COL_REMOVABLE, &removable,
                      -1);
-  g_assert (col_data != NULL);
 
   if (!removable)
     return;
 
+  g_assert (col_data != NULL);
+
   path = col_data;
 
   error = NULL;
@@ -2626,6 +2764,16 @@ bookmarks_check_add_sensitivity (GtkFileChooserDefault *impl)
   gboolean active;
   gchar *tip;
 
+  if (impl->operation_mode == OPERATION_MODE_SEARCH)
+    {
+      gtk_widget_set_sensitive (impl->browse_shortcuts_add_button, FALSE);
+      
+      if (impl->browse_files_popup_menu_add_shortcut_item)
+       gtk_widget_set_sensitive (impl->browse_files_popup_menu_add_shortcut_item, FALSE);
+      
+      return;
+    }
+
   selection_check (impl, &num_selected, NULL, &all_folders);
 
   if (num_selected == 0)
@@ -3139,7 +3287,7 @@ shortcuts_reorder (GtkFileChooserDefault *impl,
 {
   GtkTreeIter iter;
   gpointer col_data;
-  gboolean is_volume;
+  ShortcutType shortcut_type;
   GtkTreePath *path;
   int old_position;
   int bookmarks_index;
@@ -3164,10 +3312,10 @@ shortcuts_reorder (GtkFileChooserDefault *impl,
   gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter,
                      SHORTCUTS_COL_NAME, &name,
                      SHORTCUTS_COL_DATA, &col_data,
-                     SHORTCUTS_COL_IS_VOLUME, &is_volume,
+                     SHORTCUTS_COL_TYPE, &shortcut_type,
                      -1);
   g_assert (col_data != NULL);
-  g_assert (!is_volume);
+  g_assert (shortcut_type == SHORTCUT_TYPE_PATH);
   
   file_path = col_data;
   file_path_copy = gtk_file_path_copy (file_path); /* removal below will free file_path, so we need a copy */
@@ -3251,17 +3399,11 @@ shortcuts_row_separator_func (GtkTreeModel *model,
                              GtkTreeIter  *iter,
                              gpointer      data)
 {
-  gint column = GPOINTER_TO_INT (data);
-  gchar *text;
+  ShortcutType shortcut_type;
 
-  gtk_tree_model_get (model, iter, column, &text, -1);
+  gtk_tree_model_get (model, iter, SHORTCUTS_COL_TYPE, &shortcut_type, -1);
   
-  if (!text)
-    return TRUE;
-
-  g_free (text);
-
-  return FALSE;
+  return shortcut_type == SHORTCUT_TYPE_SEPARATOR;
 }
 
 /* Since GtkTreeView has a keybinding attached to '/', we need to catch
@@ -3506,7 +3648,7 @@ shortcuts_list_create (GtkFileChooserDefault *impl)
   /* Accessible object name for the file chooser's shortcuts pane */
   atk_object_set_name (gtk_widget_get_accessible (impl->browse_shortcuts_tree_view), _("Places"));
 
-  gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), impl->shortcuts_filter_model);
+  gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), impl->shortcuts_pane_filter_model);
 
   gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view),
                                          GDK_BUTTON1_MASK,
@@ -3579,8 +3721,7 @@ shortcuts_list_create (GtkFileChooserDefault *impl)
 
   gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view),
                                        shortcuts_row_separator_func,
-                                       GINT_TO_POINTER (SHORTCUTS_COL_NAME),
-                                       NULL);
+                                       NULL, NULL);
 
   gtk_tree_view_append_column (GTK_TREE_VIEW (impl->browse_shortcuts_tree_view), column);
 
@@ -3951,6 +4092,8 @@ file_list_update_popup_menu (GtkFileChooserDefault *impl)
 {
   file_list_build_popup_menu (impl);
 
+  /* FMQ: handle OPERATION_MODE_SEARCH */
+
   /* The sensitivity of the Add to Bookmarks item is set in
    * bookmarks_check_add_sensitivity()
    */
@@ -4050,6 +4193,28 @@ list_button_press_event_cb (GtkWidget             *widget,
   return TRUE;
 }
 
+/* Sets the sort column IDs for the file list based on the operation mode */
+static void
+file_list_set_sort_column_ids (GtkFileChooserDefault *impl)
+{
+  int name_id, mtime_id;
+
+  if (impl->operation_mode == OPERATION_MODE_BROWSE)
+    {
+      name_id = FILE_LIST_COL_NAME;
+      mtime_id = FILE_LIST_COL_MTIME;
+    }
+  else
+    {
+      name_id = SEARCH_MODEL_COL_PATH;
+      mtime_id = SEARCH_MODEL_COL_STAT;
+    }
+
+  gtk_tree_view_column_set_sort_column_id (impl->list_name_column, name_id);
+  gtk_tree_view_column_set_sort_column_id (impl->list_mtime_column, mtime_id);
+}
+
+
 /* Creates the widgets for the file list */
 static GtkWidget *
 create_file_list (GtkFileChooserDefault *impl)
@@ -4063,7 +4228,7 @@ create_file_list (GtkFileChooserDefault *impl)
 
   swin = gtk_scrolled_window_new (NULL, NULL);
   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swin),
-                                 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
+                                 GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
   gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (swin),
                                       GTK_SHADOW_IN);
 
@@ -4153,6 +4318,7 @@ create_file_list (GtkFileChooserDefault *impl)
   gtk_tree_view_column_set_sort_column_id (column, FILE_LIST_COL_SIZE);
   gtk_tree_view_append_column (GTK_TREE_VIEW (impl->browse_files_tree_view), column);
 #endif
+
   /* Modification time column */
 
   column = gtk_tree_view_column_new ();
@@ -4163,8 +4329,11 @@ create_file_list (GtkFileChooserDefault *impl)
   gtk_tree_view_column_pack_start (column, renderer, TRUE);
   gtk_tree_view_column_set_cell_data_func (column, renderer,
                                           list_mtime_data_func, impl, NULL);
-  gtk_tree_view_column_set_sort_column_id (column, FILE_LIST_COL_MTIME);
   gtk_tree_view_append_column (GTK_TREE_VIEW (impl->browse_files_tree_view), column);
+  impl->list_mtime_column = column;
+  
+  file_list_set_sort_column_ids (impl);
+
   gtk_widget_show_all (swin);
 
   return swin;
@@ -4272,9 +4441,59 @@ save_folder_combo_changed_cb (GtkComboBox           *combo,
     return;
 
   if (gtk_combo_box_get_active_iter (combo, &iter))
-    shortcuts_activate_iter (impl, &iter);
+    {
+      GtkTreeIter child_iter;
+      
+      gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (impl->shortcuts_combo_filter_model),
+                                                       &child_iter,
+                                                       &iter);
+      shortcuts_activate_iter (impl, &child_iter);
+    }
 }
 
+/* Filter function used to filter out the Search item and its separator.  
+ * Used for the "Save in folder" combo box, so that these items do not appear in it.
+ */
+static gboolean
+shortcuts_combo_filter_func (GtkTreeModel *model,
+                            GtkTreeIter  *iter,
+                            gpointer      data)
+{
+  GtkFileChooserDefault *impl;
+  GtkTreePath *tree_path;
+  gint *indices;
+  int idx;
+  gboolean retval;
+
+  impl = GTK_FILE_CHOOSER_DEFAULT (data);
+
+  g_assert (model == GTK_TREE_MODEL (impl->shortcuts_model));
+
+  tree_path = gtk_tree_model_get_path (GTK_TREE_MODEL (impl->shortcuts_model), iter);
+  g_assert (tree_path != NULL);
+
+  indices = gtk_tree_path_get_indices (tree_path);
+
+  retval = TRUE;
+
+  if (impl->has_search)
+    {
+      idx = shortcuts_get_index (impl, SHORTCUTS_SEARCH);
+      if (idx == indices[0])
+        retval = FALSE;
+      else
+        {
+          idx = shortcuts_get_index (impl, SHORTCUTS_SEARCH_SEPARATOR);
+          if (idx == indices[0])
+           retval = FALSE;
+        }
+    }
+
+  gtk_tree_path_free (tree_path);
+
+  return retval;
+ }
+
 /* Creates the combo box with the save folders */
 static GtkWidget *
 save_folder_combo_create (GtkFileChooserDefault *impl)
@@ -4282,8 +4501,14 @@ save_folder_combo_create (GtkFileChooserDefault *impl)
   GtkWidget *combo;
   GtkCellRenderer *cell;
 
+  impl->shortcuts_combo_filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL (impl->shortcuts_model), NULL);
+  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (impl->shortcuts_combo_filter_model),
+                                         shortcuts_combo_filter_func,
+                                         impl,
+                                         NULL);
+
   combo = g_object_new (GTK_TYPE_COMBO_BOX,
-                       "model", impl->shortcuts_model,
+                       "model", impl->shortcuts_combo_filter_model,
                        "focus-on-click", FALSE,
                         NULL);
   gtk_widget_show (combo);
@@ -4305,8 +4530,7 @@ save_folder_combo_create (GtkFileChooserDefault *impl)
 
   gtk_combo_box_set_row_separator_func (GTK_COMBO_BOX (combo),
                                        shortcuts_row_separator_func,
-                                       GINT_TO_POINTER (SHORTCUTS_COL_NAME),
-                                       NULL);
+                                       NULL, NULL);
 
   g_signal_connect (combo, "changed",
                    G_CALLBACK (save_folder_combo_changed_cb), impl);
@@ -4651,6 +4875,7 @@ browse_widgets_create (GtkFileChooserDefault *impl)
   hbox = gtk_hbox_new (FALSE, 12);
   gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
   gtk_widget_show (hbox);
+  impl->browse_path_bar_hbox = hbox;
 
   location_button_create (impl);
   gtk_box_pack_start (GTK_BOX (hbox), impl->location_button, FALSE, FALSE, 0);
@@ -5235,6 +5460,9 @@ gtk_file_chooser_default_dispose (GObject *object)
       impl->shortcuts_activate_iter_handle = NULL;
     }
 
+  if (impl->operation_mode == OPERATION_MODE_SEARCH)
+    search_stop_searching (impl);
+
   remove_settings_signal (impl, gtk_widget_get_screen (GTK_WIDGET (impl)));
 
   G_OBJECT_CLASS (_gtk_file_chooser_default_parent_class)->dispose (object);
@@ -5512,30 +5740,40 @@ gtk_file_chooser_default_map (GtkWidget *widget)
 
   GTK_WIDGET_CLASS (_gtk_file_chooser_default_parent_class)->map (widget);
 
-  switch (impl->reload_state)
+  if (impl->operation_mode == OPERATION_MODE_BROWSE)
     {
-    case RELOAD_EMPTY:
-      /* The user didn't explicitly give us a folder to display, so we'll use the cwd */
-      current_working_dir = g_get_current_dir ();
-      gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (impl), current_working_dir);
-      g_free (current_working_dir);
-      break;
-
-    case RELOAD_HAS_FOLDER:
-      /* Nothing; we are already loading or loaded, so we don't need to reload */
-      break;
-
-    case RELOAD_WAS_UNMAPPED:
-      /* Just reload the current folder; else continue the pending load. */
-      if (impl->current_folder)
+      switch (impl->reload_state)
         {
-          pending_select_paths_store_selection (impl);
-          change_folder_and_display_error (impl, impl->current_folder, FALSE);
-       }
-      break;
-
-    default:
-      g_assert_not_reached ();
+        case RELOAD_EMPTY:
+          /* The user didn't explicitly give us a folder to
+           * display, so we'll use the cwd
+           */
+          current_working_dir = g_get_current_dir ();
+          gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (impl),
+                                               current_working_dir);
+          g_free (current_working_dir);
+          break;
+        
+        case RELOAD_HAS_FOLDER:
+          /* Nothing; we are already loading or loaded, so we
+           * don't need to reload
+           */
+          break;
+
+        case RELOAD_WAS_UNMAPPED:
+          /* Just reload the current folder; else continue
+           * the pending load.
+           */
+          if (impl->current_folder)
+            {
+              pending_select_paths_store_selection (impl);
+              change_folder_and_display_error (impl, impl->current_folder, FALSE);
+            }
+          break;
+
+        default:
+          g_assert_not_reached ();
+      }
     }
 
   bookmarks_changed_cb (impl->file_system, impl);
@@ -6003,12 +6241,6 @@ pending_select_paths_process (GtkFileChooserDefault *impl)
        * but rather on behalf of something else like GtkFileChooserButton.  In
        * that case, the chooser's selection should be what the caller expects,
        * as the user can't see that something else got selected.  See bug #165264.
-       *
-       * Also, we don't select the first file if we are not in OPEN mode.  Doing
-       * so would change the contents of the filename entry for SAVE or
-       * CREATE_FOLDER, which is undesired; in SELECT_FOLDER, we don't want to
-       * select a *different* folder from the one into which the user just
-       * navigated.
        */
       if (GTK_WIDGET_MAPPED (impl) && impl->action == GTK_FILE_CHOOSER_ACTION_OPEN)
        browse_files_select_first_row (impl);
@@ -6055,17 +6287,11 @@ browse_files_model_finished_loading_cb (GtkFileSystemModel    *model,
   profile_end ("end", NULL);
 }
 
-/* Gets rid of the old list model and creates a new one for the current folder */
-static gboolean
-set_list_model (GtkFileChooserDefault *impl,
-               GError               **error)
+static void
+stop_loading_and_clear_list_model (GtkFileChooserDefault *impl)
 {
-  g_assert (impl->current_folder != NULL);
-
-  profile_start ("start", NULL);
-
   load_remove_timer (impl); /* This changes the state to LOAD_EMPTY */
-
+  
   if (impl->browse_files_model)
     {
       g_object_unref (impl->browse_files_model);
@@ -6077,6 +6303,20 @@ set_list_model (GtkFileChooserDefault *impl,
       g_object_unref (impl->sort_model);
       impl->sort_model = NULL;
     }
+  
+  gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_files_tree_view), NULL);
+}
+
+/* Gets rid of the old list model and creates a new one for the current folder */
+static gboolean
+set_list_model (GtkFileChooserDefault *impl,
+               GError               **error)
+{
+  g_assert (impl->current_folder != NULL);
+
+  profile_start ("start", NULL);
+
+  stop_loading_and_clear_list_model (impl);
 
   set_busy_cursor (impl, TRUE);
   gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_files_tree_view), NULL);
@@ -6151,6 +6391,9 @@ update_chooser_entry (GtkFileChooserDefault *impl)
   struct update_chooser_entry_selected_foreach_closure closure;
   const char *file_part;
 
+  if (impl->operation_mode == OPERATION_MODE_SEARCH || !impl->location_entry)
+    return;
+
   if (!(impl->action == GTK_FILE_CHOOSER_ACTION_SAVE
        || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER
        || ((impl->action == GTK_FILE_CHOOSER_ACTION_OPEN
@@ -6172,35 +6415,46 @@ update_chooser_entry (GtkFileChooserDefault *impl)
     }
   else if (closure.num_selected == 1)
     {
-      GtkTreeIter child_iter;
-      const GtkFileInfo *info;
-      gboolean change_entry;
+      if (impl->operation_mode == OPERATION_MODE_BROWSE)
+        {
+          GtkTreeIter child_iter;
+          const GtkFileInfo *info;
+          gboolean change_entry;
 
-      gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model,
-                                                     &child_iter,
-                                                     &closure.first_selected_iter);
+          gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model,
+                                                          &child_iter,
+                                                          &closure.first_selected_iter);
 
-      info = _gtk_file_system_model_get_info (impl->browse_files_model, &child_iter);
+          info = _gtk_file_system_model_get_info (impl->browse_files_model, &child_iter);
 
-      /* If the cursor moved to the row of the newly created folder, 
-       * retrieving info will return NULL.
-       */
-      if (!info)
-       return;
+          /* If the cursor moved to the row of the newly created folder, 
+           * retrieving info will return NULL.
+           */
+          if (!info)
+            return;
 
-      g_free (impl->browse_files_last_selected_name);
-      impl->browse_files_last_selected_name = g_strdup (gtk_file_info_get_display_name (info));
+          g_free (impl->browse_files_last_selected_name);
+          impl->browse_files_last_selected_name =
+            g_strdup (gtk_file_info_get_display_name (info));
 
-      if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN
-         || impl->action == GTK_FILE_CHOOSER_ACTION_SAVE)
-       change_entry = !gtk_file_info_get_is_folder (info); /* We don't want the name to change when clicking on a folder... */
-      else
-       change_entry = TRUE;                                /* ... unless we are in one of the folder modes */
+          if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN ||
+              impl->action == GTK_FILE_CHOOSER_ACTION_SAVE)
+            change_entry = !gtk_file_info_get_is_folder (info); /* We don't want the name to change when clicking on a folder... */
+          else
+           change_entry = TRUE;                                /* ... unless we are in one of the folder modes */
 
-      if (change_entry)
-       _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), impl->browse_files_last_selected_name);
+          if (change_entry)
+           _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->location_entry),
+                                                   impl->browse_files_last_selected_name);
 
-      return;
+        }
+      else
+        {
+          gtk_tree_model_get (GTK_TREE_MODEL (impl->search_model),
+                              &closure.first_selected_iter,
+                              SEARCH_MODEL_COL_DISPLAY_NAME, &file_part,
+                              -1);
+        }
     }
   else
     {
@@ -6414,6 +6668,8 @@ gtk_file_chooser_default_update_current_folder (GtkFileChooser    *chooser,
 
   profile_start ("start", (char *) path);
 
+ search_switch_to_browse_mode (impl);
+
   g_assert (path != NULL);
 
   if (impl->local_only &&
@@ -6456,6 +6712,9 @@ gtk_file_chooser_default_get_current_folder (GtkFileChooser *chooser)
 {
   GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser);
 
+  if (impl->operation_mode == OPERATION_MODE_SEARCH)
+    return NULL;
   if (impl->reload_state == RELOAD_EMPTY)
     {
       char *current_working_dir;
@@ -6516,9 +6775,9 @@ gtk_file_chooser_default_select_path (GtkFileChooser    *chooser,
     return FALSE;
 
   if (!parent_path)
-    return _gtk_file_chooser_set_current_folder_path (chooser, path, error);
+   return _gtk_file_chooser_set_current_folder_path (chooser, path, error);
 
-  if (impl->load_state == LOAD_EMPTY)
+  if (impl->operation_mode == OPERATION_MODE_SEARCH || impl->load_state == LOAD_EMPTY)
     same_path = FALSE;
   else
     {
@@ -6614,6 +6873,16 @@ static void
 gtk_file_chooser_default_select_all (GtkFileChooser *chooser)
 {
   GtkFileChooserDefault *impl = GTK_FILE_CHOOSER_DEFAULT (chooser);
+
+  if (impl->operation_mode == OPERATION_MODE_SEARCH)
+    {
+      GtkTreeSelection *selection;
+      
+      selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view));
+      gtk_tree_selection_select_all (selection);
+      return;
+    }
+
   if (impl->select_multiple)
     gtk_tree_model_foreach (GTK_TREE_MODEL (impl->sort_model), 
                            maybe_select, impl);
@@ -6753,6 +7022,10 @@ gtk_file_chooser_default_get_paths (GtkFileChooser *chooser)
   struct get_paths_closure info;
   GtkWindow *toplevel;
   GtkWidget *current_focus;
+  gboolean file_list_seen;
+
+  if (impl->operation_mode == OPERATION_MODE_SEARCH)
+    return search_get_selected_paths (impl);
 
   info.impl = impl;
   info.result = NULL;
@@ -6764,12 +7037,14 @@ gtk_file_chooser_default_get_paths (GtkFileChooser *chooser)
   else
     current_focus = NULL;
 
+  file_list_seen = FALSE;
   if (current_focus == impl->browse_files_tree_view)
     {
       GtkTreeSelection *selection;
 
     file_list:
 
+      file_list_seen = TRUE;
       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view));
       gtk_tree_selection_selected_foreach (selection, get_paths_foreach, &info);
 
@@ -6804,8 +7079,12 @@ gtk_file_chooser_default_get_paths (GtkFileChooser *chooser)
          return NULL;
        }
 
-      g_assert (info.path_from_entry != NULL);
-      info.result = g_slist_prepend (info.result, info.path_from_entry);
+      if (info.path_from_entry)
+        info.result = g_slist_prepend (info.result, info.path_from_entry);
+      else if (!file_list_seen) 
+        goto file_list;
+      else
+        return NULL;
     }
   else if (impl->toplevel_last_focus_widget == impl->browse_files_tree_view)
     goto file_list;
@@ -6974,7 +7253,7 @@ add_shortcut_get_info_cb (GtkFileSystemHandle *handle,
 
   pos = shortcuts_get_pos_for_shortcut_folder (data->impl, data->impl->num_shortcuts);
 
-  shortcuts_insert_path (data->impl, pos, FALSE, NULL, data->path, NULL, FALSE, SHORTCUTS_SHORTCUTS);
+  shortcuts_insert_path (data->impl, pos, SHORTCUT_TYPE_PATH, NULL, data->path, NULL, FALSE, SHORTCUTS_SHORTCUTS);
 
 out:
   g_object_unref (data->impl);
@@ -7088,15 +7367,15 @@ gtk_file_chooser_default_remove_shortcut_folder (GtkFileChooser    *chooser,
   for (i = 0; i < impl->num_shortcuts; i++)
     {
       gpointer col_data;
-      gboolean is_volume;
+      ShortcutType shortcut_type;
       GtkFilePath *shortcut;
 
       gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter,
                          SHORTCUTS_COL_DATA, &col_data,
-                         SHORTCUTS_COL_IS_VOLUME, &is_volume,
+                         SHORTCUTS_COL_TYPE, &shortcut_type,
                          -1);
       g_assert (col_data != NULL);
-      g_assert (!is_volume);
+      g_assert (shortcut_type == SHORTCUT_TYPE_PATH);
 
       shortcut = col_data;
       if (gtk_file_path_compare (shortcut, path) == 0)
@@ -7145,15 +7424,15 @@ gtk_file_chooser_default_list_shortcut_folders (GtkFileChooser *chooser)
   for (i = 0; i < impl->num_shortcuts; i++)
     {
       gpointer col_data;
-      gboolean is_volume;
+      ShortcutType shortcut_type;
       GtkFilePath *shortcut;
 
       gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), &iter,
                          SHORTCUTS_COL_DATA, &col_data,
-                         SHORTCUTS_COL_IS_VOLUME, &is_volume,
+                         SHORTCUTS_COL_TYPE, &shortcut_type,
                          -1);
       g_assert (col_data != NULL);
-      g_assert (!is_volume);
+      g_assert (shortcut_type == SHORTCUT_TYPE_PATH);
 
       shortcut = col_data;
       list = g_slist_prepend (list, gtk_file_path_copy (shortcut));
@@ -7500,6 +7779,23 @@ should_respond_after_confirm_overwrite (GtkFileChooserDefault *impl,
     }
 }
 
+/* Gives the focus to the browse tree view only if it is visible */
+static void
+focus_browse_tree_view_if_possible (GtkFileChooserDefault *impl)
+{
+  gboolean do_focus;
+
+  if ((impl->action == GTK_FILE_CHOOSER_ACTION_SAVE
+       || impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
+      && !gtk_expander_get_expanded (GTK_EXPANDER (impl->save_expander)))
+    do_focus = FALSE;
+  else
+    do_focus = TRUE;
+
+  if (do_focus)
+    gtk_widget_grab_focus (impl->browse_files_tree_view);
+}
+
 static void
 action_create_folder_cb (GtkFileSystemHandle *handle,
                         const GtkFilePath   *path,
@@ -7765,6 +8061,9 @@ gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed)
 
       g_assert (impl->action >= GTK_FILE_CHOOSER_ACTION_OPEN && impl->action <= GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER);
 
+      if (impl->operation_mode == OPERATION_MODE_SEARCH)
+       return search_should_respond (impl);
+
       selection_check (impl, &num_selected, &all_files, &all_folders);
 
       if (num_selected > 2)
@@ -7900,7 +8199,7 @@ gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed)
        {
          shortcuts_activate_iter (impl, &iter);
          
-         gtk_widget_grab_focus (impl->browse_files_tree_view);
+         focus_browse_tree_view_if_possible (impl);
        }
       else
        goto file_list;
@@ -7914,6 +8213,11 @@ gtk_file_chooser_default_should_respond (GtkFileChooserEmbed *chooser_embed)
        */
       goto file_list;
     }
+  else if (impl->operation_mode == OPERATION_MODE_SEARCH && impl->toplevel_last_focus_widget == impl->search_entry)
+    {
+      search_entry_activate_cb (GTK_ENTRY (impl->search_entry), impl);
+      return FALSE;
+    }
   else if (impl->location_entry && impl->toplevel_last_focus_widget == impl->location_entry)
     {
       /* The focus is on a dialog's action area button, *and* the widget that
@@ -7963,29 +8267,467 @@ gtk_file_chooser_default_initial_focus (GtkFileChooserEmbed *chooser_embed)
   gtk_widget_grab_focus (widget);
 }
 
+/* Callback used from gtk_tree_selection_selected_foreach(); gets the selected GtkFilePaths */
 static void
-set_current_filter (GtkFileChooserDefault *impl,
-                   GtkFileFilter         *filter)
+search_selected_foreach_get_path_cb (GtkTreeModel *model,
+                                    GtkTreePath  *path,
+                                    GtkTreeIter  *iter,
+                                    gpointer      data)
 {
-  if (impl->current_filter != filter)
-    {
-      int filter_index;
+  GSList **list;
+  const GtkFilePath *file_path;
+  GtkFilePath *file_path_copy;
 
-      /* NULL filters are allowed to reset to non-filtered status
-       */
-      filter_index = g_slist_index (impl->filters, filter);
-      if (impl->filters && filter && filter_index < 0)
-       return;
+  list = data;
 
-      if (impl->current_filter)
-       g_object_unref (impl->current_filter);
-      impl->current_filter = filter;
-      if (impl->current_filter)
-       {
-         g_object_ref_sink (impl->current_filter);
-       }
+  gtk_tree_model_get (model, iter, SEARCH_MODEL_COL_PATH, &file_path, -1);
+  file_path_copy = gtk_file_path_copy (file_path);
+  *list = g_slist_prepend (*list, file_path_copy);
+}
 
-      if (impl->filters)
+/* Constructs a list of the selected paths in search mode */
+static GSList *
+search_get_selected_paths (GtkFileChooserDefault *impl)
+{
+  GSList *result;
+  GtkTreeSelection *selection;
+
+  result = NULL;
+
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view));
+  gtk_tree_selection_selected_foreach (selection, search_selected_foreach_get_path_cb, &result);
+  result = g_slist_reverse (result);
+
+  return result;
+}
+
+/* Called from ::should_respond().  We return whether there are selected files
+ * in the search list.
+ */
+static gboolean
+search_should_respond (GtkFileChooserDefault *impl)
+{
+  GtkTreeSelection *selection;
+
+  g_assert (impl->operation_mode == OPERATION_MODE_SEARCH);
+
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (impl->browse_files_tree_view));
+  return (gtk_tree_selection_count_selected_rows (selection) != 0);
+}
+
+
+/* Adds one hit from the search engine to the search_model */
+static void
+search_add_hit (GtkFileChooserDefault *impl,
+               gchar                 *uri)
+{
+  GtkFilePath *path;
+  char *filename;
+  char *display_name;
+  char *collation_key;
+  struct stat statbuf;
+  struct stat *statbuf_copy;
+  GtkTreeIter iter;
+
+  path = gtk_file_system_uri_to_path (impl->file_system, uri);
+  if (!path)
+    return;
+
+  filename = gtk_file_system_path_to_filename (impl->file_system, path);
+  if (!filename)
+    {
+      gtk_file_path_free (path);
+      return;
+    }
+
+  if (stat (filename, &statbuf) != 0)
+    {
+      gtk_file_path_free (path);
+      g_free (filename);
+      return;
+    }
+
+  statbuf_copy = g_new (struct stat, 1);
+  *statbuf_copy = statbuf;
+
+  display_name = g_filename_display_name (filename);
+  collation_key = g_utf8_collate_key_for_filename (display_name, -1);
+
+  gtk_list_store_insert_with_values (impl->search_model, &iter, -1,
+                                    SEARCH_MODEL_COL_PATH, path,
+                                    SEARCH_MODEL_COL_DISPLAY_NAME, display_name,
+                                    SEARCH_MODEL_COL_COLLATION_KEY, collation_key,
+                                    SEARCH_MODEL_COL_STAT, statbuf_copy,
+                                    -1);
+}
+
+/* Callback used from GtkSearchEngine when we get new hits */
+static void
+search_engine_hits_added_cb (GtkSearchEngine *engine,
+                            GList           *hits,
+                            gpointer         data)
+{
+  GtkFileChooserDefault *impl;
+  GList *l;
+  
+  impl = GTK_FILE_CHOOSER_DEFAULT (data);
+
+  for (l = hits; l; l = l->next)
+    search_add_hit (impl, (gchar*)l->data);
+}
+
+/* Callback used from GtkSearchEngine when the query is done running */
+static void
+search_engine_finished_cb (GtkSearchEngine *engine,
+                          gpointer         data)
+{
+  GtkFileChooserDefault *impl;
+  
+  impl = GTK_FILE_CHOOSER_DEFAULT (data);
+  
+  /* FMQ: if search was empty, say that we got no hits */
+
+  set_busy_cursor (impl, FALSE);
+}
+
+/* Displays a generic error when we cannot create a GtkSearchEngine.  
+ * It would be better if _gtk_search_engine_new() gave us a GError 
+ * with a better message, but it doesn't do that right now.
+ */
+static void
+search_error_could_not_create_client (GtkFileChooserDefault *impl)
+{
+  error_message (impl,
+                _("Could not start the search process"),
+                _("The program was not able to create a connection to the indexer "
+                  "daemon.  Please make sure it is running."));
+}
+
+static void
+search_engine_error_cb (GtkSearchEngine *engine,
+                       const gchar     *message,
+                       gpointer         data)
+{
+  GtkFileChooserDefault *impl;
+  
+  impl = GTK_FILE_CHOOSER_DEFAULT (data);
+
+  search_stop_searching (impl);
+  error_message (impl, _("Could not send the search request"), message);
+
+  set_busy_cursor (impl, FALSE);
+}
+
+/* Frees the data in the search_model */
+static void
+search_clear_model (GtkFileChooserDefault *impl, 
+                   gboolean               remove_from_treeview)
+{
+  GtkTreeIter iter;
+
+  if (!impl->search_model)
+    return;
+
+  if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (impl->search_model), &iter))
+    do
+      {
+       GtkFilePath *path;
+       char *display_name;
+       char *collation_key;
+       struct stat *statbuf;
+
+       gtk_tree_model_get (GTK_TREE_MODEL (impl->search_model), &iter,
+                           SEARCH_MODEL_COL_PATH, &path,
+                           SEARCH_MODEL_COL_DISPLAY_NAME, &display_name,
+                           SEARCH_MODEL_COL_COLLATION_KEY, &collation_key,
+                           SEARCH_MODEL_COL_STAT, &statbuf,
+                           -1);
+
+       gtk_file_path_free (path);
+       g_free (display_name);
+       g_free (collation_key);
+       g_free (statbuf);
+      }
+    while (gtk_tree_model_iter_next (GTK_TREE_MODEL (impl->search_model), &iter));
+
+  g_object_unref (impl->search_model);
+  impl->search_model = NULL;
+
+  if (remove_from_treeview)
+    gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_files_tree_view), NULL);
+}
+
+/* Stops any ongoing searches; does not touch the search_model */
+static void
+search_stop_searching (GtkFileChooserDefault *impl)
+{
+  if (impl->search_query)
+    {
+      g_object_unref (impl->search_query);
+      impl->search_query = NULL;
+    }
+
+  if (impl->search_engine)
+    {
+      g_object_unref (impl->search_engine);
+      impl->search_engine = NULL;
+    }
+}
+
+/* Stops any pending searches, clears the file list, and switches back to OPERATION_MODE_BROWSE */
+static void
+search_switch_to_browse_mode (GtkFileChooserDefault *impl)
+{
+  if (impl->operation_mode == OPERATION_MODE_BROWSE)
+    return;
+
+  search_stop_searching (impl);
+  search_clear_model (impl, TRUE);
+
+  gtk_widget_destroy (impl->search_hbox);
+  impl->search_hbox = NULL;
+  impl->search_entry = NULL;
+
+  gtk_widget_show (impl->browse_path_bar);
+  gtk_widget_show (impl->browse_new_folder_button);
+
+  if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN
+      || impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
+    {
+      gtk_widget_show (impl->location_button);
+
+      if (impl->location_mode == LOCATION_MODE_FILENAME_ENTRY)
+       gtk_widget_show (impl->location_entry_box);
+    }
+
+  impl->operation_mode = OPERATION_MODE_BROWSE;
+
+  file_list_set_sort_column_ids (impl);
+}
+
+/* Sort callback from the path column */
+static gint
+search_column_path_sort_func (GtkTreeModel *model,
+                             GtkTreeIter  *a,
+                             GtkTreeIter  *b,
+                             gpointer      user_data)
+{
+  const char *collation_key_a, *collation_key_b;
+
+  gtk_tree_model_get (model, a, SEARCH_MODEL_COL_COLLATION_KEY, &collation_key_a, -1);
+  gtk_tree_model_get (model, b, SEARCH_MODEL_COL_COLLATION_KEY, &collation_key_b, -1);
+
+  return strcmp (collation_key_a, collation_key_b);
+}
+
+/* Sort callback from the modification time column */
+static gint
+search_column_mtime_sort_func (GtkTreeModel *model,
+                              GtkTreeIter  *a,
+                              GtkTreeIter  *b,
+                              gpointer      user_data)
+{
+  const struct stat *statbuf_a, *statbuf_b;
+
+  /* Note that although we store a whole struct stat in the model, we only
+   * compare the mtime here.  If we add another column relative to a struct stat
+   * (e.g. a file size column), we'll want another sort callback similar to this
+   * one as well.
+   */
+
+  gtk_tree_model_get (model, a, SEARCH_MODEL_COL_STAT, &statbuf_a, -1);
+  gtk_tree_model_get (model, b, SEARCH_MODEL_COL_STAT, &statbuf_b, -1);
+
+  if (statbuf_a->st_mtime < statbuf_b->st_mtime)
+    return -1;
+  else if (statbuf_a->st_mtime > statbuf_b->st_mtime)
+    return 1;
+  else
+    return 0;
+}
+
+/* Creates the search_model and puts it in the tree view */
+static void
+search_setup_model (GtkFileChooserDefault *impl)
+{
+  g_assert (impl->search_model == NULL);
+
+  /* We store these columns in the search model:
+   *
+   * SEARCH_MODEL_COL_PATH - a GtkFilePath for the hit's URI (stored as a pointer, not as a GTK_TYPE_FILE_PATH)
+   * SEARCH_MODEL_COL_DISPLAY_NAME - a string with the display name (stored as a pointer, not as a G_TYPE_STRING)
+   * SEARCH_MODEL_COL_COLLATION_KEY - collation key for the filename (stored as a pointer, not as a G_TYPE_STRING)
+   * SEARCH_MODEL_COL_STAT - pointer to a struct stat
+   *
+   * Keep this in sync with the enumeration defined near the beginning of this file.
+   */
+  impl->search_model = gtk_list_store_new (4,
+                                          G_TYPE_POINTER,
+                                          G_TYPE_POINTER,
+                                          G_TYPE_POINTER,
+                                          G_TYPE_POINTER);
+  gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->search_model),
+                                  SEARCH_MODEL_COL_PATH,
+                                  search_column_path_sort_func,
+                                  impl,
+                                  NULL);
+  gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (impl->search_model),
+                                  SEARCH_MODEL_COL_STAT,
+                                  search_column_mtime_sort_func,
+                                  impl,
+                                  NULL);
+
+  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (impl->search_model),
+                                       SEARCH_MODEL_COL_STAT,
+                                       GTK_SORT_DESCENDING);
+
+  gtk_tree_view_set_model (GTK_TREE_VIEW (impl->browse_files_tree_view), GTK_TREE_MODEL (impl->search_model));
+}
+
+/* Creates a new query with the specified text and launches it */
+static void
+search_start_query (GtkFileChooserDefault *impl,
+                   const gchar           *query_text)
+{
+  search_stop_searching (impl);
+  search_clear_model (impl, TRUE);
+  search_setup_model (impl);
+  set_busy_cursor (impl, TRUE);
+
+  if (impl->search_engine == NULL)
+    impl->search_engine = _gtk_search_engine_new ();
+
+  if (!impl->search_engine)
+    {
+      set_busy_cursor (impl, FALSE);
+      search_error_could_not_create_client (impl); /* lame; we don't get an error code or anything */
+      return;
+    }
+
+  impl->search_query = _gtk_query_new ();
+  _gtk_query_set_text (impl->search_query, query_text);
+  _gtk_search_engine_set_query (impl->search_engine, impl->search_query);
+
+  g_signal_connect (impl->search_engine, "hits-added",
+                   G_CALLBACK (search_engine_hits_added_cb), impl);
+  g_signal_connect (impl->search_engine, "finished",
+                   G_CALLBACK (search_engine_finished_cb), impl);
+  g_signal_connect (impl->search_engine, "error",
+                   G_CALLBACK (search_engine_error_cb), impl);
+
+  _gtk_search_engine_start (impl->search_engine);
+}
+
+/* Callback used when the user presses Enter while typing on the search entry; starts the query */
+static void
+search_entry_activate_cb (GtkEntry *entry,
+                         gpointer data)
+{
+  GtkFileChooserDefault *impl;
+  const char *text;
+
+  impl = GTK_FILE_CHOOSER_DEFAULT (data);
+
+  text = gtk_entry_get_text (GTK_ENTRY (impl->search_entry));
+  if (strlen (text) == 0)
+    return;
+
+  search_start_query (impl, text);
+}
+
+/* Hides the path bar and creates the search entry */
+static void
+search_setup_widgets (GtkFileChooserDefault *impl)
+{
+  GtkWidget *label;
+  gchar *text;
+
+  impl->search_hbox = gtk_hbox_new (FALSE, 12);
+
+  /* Label */
+
+  label = gtk_label_new (NULL);
+  text = g_strdup_printf ("<b>%s</b>", _("Search:"));
+  gtk_label_set_markup (GTK_LABEL (label), text);
+  g_free (text);
+  gtk_box_pack_start (GTK_BOX (impl->search_hbox), label, FALSE, FALSE, 0);
+
+  /* Entry */
+
+  impl->search_entry = gtk_entry_new ();
+  g_signal_connect (impl->search_entry, "activate",
+                   G_CALLBACK (search_entry_activate_cb),
+                   impl);
+  gtk_box_pack_start (GTK_BOX (impl->search_hbox), impl->search_entry, TRUE, TRUE, 0);
+
+  gtk_widget_hide (impl->browse_path_bar);
+  gtk_widget_hide (impl->browse_new_folder_button);
+
+  /* Box for search widgets */
+
+  gtk_box_pack_start (GTK_BOX (impl->browse_path_bar_hbox), impl->search_hbox, TRUE, TRUE, 0);
+  gtk_widget_show_all (impl->search_hbox);
+
+  /* Hide the location widgets temporarily */
+
+  if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN
+      || impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
+    {
+      gtk_widget_hide (impl->location_button);
+      gtk_widget_hide (impl->location_entry_box);
+    }
+
+  gtk_widget_grab_focus (impl->search_entry);
+
+  /* FMQ: hide the filter combo? */
+}
+
+/* Main entry point to the searching functions; this gets called when the user
+ * activates the Search shortcut.
+ */
+static void
+search_activate (GtkFileChooserDefault *impl)
+{
+  if (impl->operation_mode == OPERATION_MODE_SEARCH)
+    {
+      gtk_widget_grab_focus (impl->search_entry);
+      return;
+    }
+
+  impl->operation_mode = OPERATION_MODE_SEARCH;
+
+  g_assert (impl->search_hbox == NULL);
+  g_assert (impl->search_entry == NULL);
+  g_assert (impl->search_model == NULL);
+
+  stop_loading_and_clear_list_model (impl);
+  search_setup_widgets (impl);
+  file_list_set_sort_column_ids (impl);
+}
+
+static void
+set_current_filter (GtkFileChooserDefault *impl,
+                   GtkFileFilter         *filter)
+{
+  if (impl->current_filter != filter)
+    {
+      int filter_index;
+
+      /* NULL filters are allowed to reset to non-filtered status
+       */
+      filter_index = g_slist_index (impl->filters, filter);
+      if (impl->filters && filter && filter_index < 0)
+       return;
+
+      if (impl->current_filter)
+       g_object_unref (impl->current_filter);
+      impl->current_filter = filter;
+      if (impl->current_filter)
+       {
+         g_object_ref_sink (impl->current_filter);
+       }
+
+      if (impl->filters)
        gtk_combo_box_set_active (GTK_COMBO_BOX (impl->filter_combo),
                                  filter_index);
 
@@ -8011,28 +8753,44 @@ check_preview_change (GtkFileChooserDefault *impl)
 {
   GtkTreePath *cursor_path;
   const GtkFilePath *new_path;
-  const GtkFileInfo *new_info;
+  const char *new_display_name;
 
   gtk_tree_view_get_cursor (GTK_TREE_VIEW (impl->browse_files_tree_view), &cursor_path, NULL);
-  if (cursor_path && impl->sort_model)
+  new_path = NULL;
+  new_display_name = NULL;
+  if (cursor_path)
     {
-      GtkTreeIter iter;
-      GtkTreeIter child_iter;
+      if (impl->operation_mode == OPERATION_MODE_BROWSE)
+       {
+         if (impl->sort_model)
+           {
+             GtkTreeIter iter;
+             GtkTreeIter child_iter;
+             const GtkFileInfo *new_info;
 
-      if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->sort_model), &iter, cursor_path))
-       g_assert_not_reached ();
+             gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->sort_model), &iter, cursor_path);
+             gtk_tree_path_free (cursor_path);
 
-      gtk_tree_path_free (cursor_path);
+             gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model, &child_iter, &iter);
 
-      gtk_tree_model_sort_convert_iter_to_child_iter (impl->sort_model, &child_iter, &iter);
+             new_path = _gtk_file_system_model_get_path (impl->browse_files_model, &child_iter);
+             new_info = _gtk_file_system_model_get_info (impl->browse_files_model, &child_iter);
+              if (new_info)
+               new_display_name = gtk_file_info_get_display_name (new_info);
+           }
+       }
+      else
+       {
+         GtkTreeIter iter;
 
-      new_path = _gtk_file_system_model_get_path (impl->browse_files_model, &child_iter);
-      new_info = _gtk_file_system_model_get_info (impl->browse_files_model, &child_iter);
-    }
-  else
-    {
-      new_path = NULL;
-      new_info = NULL;
+         gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->search_model), &iter, cursor_path);
+         gtk_tree_path_free (cursor_path);
+
+         gtk_tree_model_get (GTK_TREE_MODEL (impl->search_model), &iter,
+                             SEARCH_MODEL_COL_PATH, &new_path,
+                             SEARCH_MODEL_COL_DISPLAY_NAME, &new_display_name,
+                             -1);
+       }
     }
 
   if (new_path != impl->preview_path &&
@@ -8048,7 +8806,7 @@ check_preview_change (GtkFileChooserDefault *impl)
       if (new_path)
        {
          impl->preview_path = gtk_file_path_copy (new_path);
-         impl->preview_display_name = g_strdup (gtk_file_info_get_display_name (new_info));
+         impl->preview_display_name = g_strdup (new_display_name);
        }
       else
        {
@@ -8099,6 +8857,8 @@ shortcuts_activate_volume_mount_cb (GtkFileSystemHandle *handle,
   if (path != NULL)
     {
       change_folder_and_display_error (impl, path, FALSE);
+      focus_browse_tree_view_if_possible (impl);
+      
       gtk_file_path_free (path);
     }
 
@@ -8117,6 +8877,8 @@ shortcuts_activate_volume (GtkFileChooserDefault *impl,
 {
   GtkFilePath *path;
 
+  search_switch_to_browse_mode (impl);
+
   /* We ref the file chooser since volume_mount() may run a main loop, and the
    * user could close the file chooser window in the meantime.
    */
@@ -8169,9 +8931,14 @@ shortcuts_activate_get_info_cb (GtkFileSystemHandle *handle,
     goto out;
 
   if (!error && gtk_file_info_get_is_folder (info))
-    change_folder_and_display_error (data->impl, data->path, FALSE);
+    {
+      change_folder_and_display_error (data->impl, data->path, FALSE);
+      focus_browse_tree_view_if_possible (data->impl);
+    }
   else
-    gtk_file_chooser_default_select_path (GTK_FILE_CHOOSER (data->impl), data->path, NULL);
+    gtk_file_chooser_default_select_path (GTK_FILE_CHOOSER (data->impl),
+                                          data->path,
+                                          NULL);
 
 out:
   g_object_unref (data->impl);
@@ -8186,26 +8953,25 @@ shortcuts_activate_iter (GtkFileChooserDefault *impl,
                         GtkTreeIter           *iter)
 {
   gpointer col_data;
-  gboolean is_volume;
+  ShortcutType shortcut_type;
 
   if (impl->location_mode == LOCATION_MODE_FILENAME_ENTRY && impl->action != GTK_FILE_CHOOSER_ACTION_SAVE)
     _gtk_file_chooser_entry_set_file_part (GTK_FILE_CHOOSER_ENTRY (impl->location_entry), "");
 
   gtk_tree_model_get (GTK_TREE_MODEL (impl->shortcuts_model), iter,
                      SHORTCUTS_COL_DATA, &col_data,
-                     SHORTCUTS_COL_IS_VOLUME, &is_volume,
+                     SHORTCUTS_COL_TYPE, &shortcut_type,
                      -1);
 
-  if (!col_data)
-    return; /* We are on a separator */
-
   if (impl->shortcuts_activate_iter_handle)
     {
       gtk_file_system_cancel_operation (impl->shortcuts_activate_iter_handle);
       impl->shortcuts_activate_iter_handle = NULL;
     }
 
-  if (is_volume)
+  if (shortcut_type == SHORTCUT_TYPE_SEPARATOR)
+    return;
+  else if (shortcut_type == SHORTCUT_TYPE_VOLUME)
     {
       GtkFileSystemVolume *volume;
 
@@ -8213,7 +8979,7 @@ shortcuts_activate_iter (GtkFileChooserDefault *impl,
 
       shortcuts_activate_volume (impl, volume);
     }
-  else
+  else if (shortcut_type == SHORTCUT_TYPE_PATH)
     {
       struct ShortcutsActivateData *data;
 
@@ -8226,6 +8992,10 @@ shortcuts_activate_iter (GtkFileChooserDefault *impl,
                                  GTK_FILE_INFO_IS_FOLDER,
                                  shortcuts_activate_get_info_cb, data);
     }
+  else if (shortcut_type == SHORTCUT_TYPE_SEARCH)
+    {
+      search_activate (impl);
+    }
 }
 
 /* Callback used when a row in the shortcuts list is activated */
@@ -8238,15 +9008,13 @@ shortcuts_row_activated_cb (GtkTreeView           *tree_view,
   GtkTreeIter iter;
   GtkTreeIter child_iter;
 
-  if (!gtk_tree_model_get_iter (impl->shortcuts_filter_model, &iter, path))
+  if (!gtk_tree_model_get_iter (impl->shortcuts_pane_filter_model, &iter, path))
     return;
 
-  gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (impl->shortcuts_filter_model),
+  gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER (impl->shortcuts_pane_filter_model),
                                                    &child_iter,
                                                    &iter);
   shortcuts_activate_iter (impl, &child_iter);
-
-  gtk_widget_grab_focus (impl->browse_files_tree_view);
 }
 
 /* Handler for GtkWidget::key-press-event on the shortcuts list */
@@ -8286,8 +9054,15 @@ shortcuts_select_func  (GtkTreeSelection  *selection,
                        gpointer           data)
 {
   GtkFileChooserDefault *impl = data;
+  GtkTreeIter filter_iter;
+  ShortcutType shortcut_type;
+
+  if (!gtk_tree_model_get_iter (impl->shortcuts_pane_filter_model, &filter_iter, path))
+    g_assert_not_reached ();
+
+  gtk_tree_model_get (impl->shortcuts_pane_filter_model, &filter_iter, SHORTCUTS_COL_TYPE, &shortcut_type, -1);
 
-  return (*gtk_tree_path_get_indices (path) != shortcuts_get_index (impl, SHORTCUTS_BOOKMARKS_SEPARATOR));
+  return shortcut_type != SHORTCUT_TYPE_SEPARATOR;
 }
 
 static gboolean
@@ -8299,6 +9074,9 @@ list_select_func  (GtkTreeSelection  *selection,
 {
   GtkFileChooserDefault *impl = data;
 
+  if (impl->operation_mode == OPERATION_MODE_SEARCH)
+    return TRUE;
+
   if (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ||
       impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
     {
@@ -8324,7 +9102,7 @@ list_selection_changed (GtkTreeSelection      *selection,
                        GtkFileChooserDefault *impl)
 {
   /* See if we are in the new folder editable row for Save mode */
-  if (impl->action == GTK_FILE_CHOOSER_ACTION_SAVE)
+  if (impl->operation_mode == OPERATION_MODE_BROWSE && impl->action == GTK_FILE_CHOOSER_ACTION_SAVE)
     {
       const GtkFileInfo *info;
       gboolean had_selection;
@@ -8339,8 +9117,10 @@ list_selection_changed (GtkTreeSelection      *selection,
 
  out:
 
+  /* TODO - Change the following functions to make them accept MODE_SEARCH */
   if (impl->location_entry)
     update_chooser_entry (impl);
+
   check_preview_change (impl);
   bookmarks_check_add_sensitivity (impl);
 
@@ -8357,6 +9137,12 @@ list_row_activated (GtkTreeView           *tree_view,
   GtkTreeIter iter, child_iter;
   const GtkFileInfo *info;
 
+  if (impl->operation_mode == OPERATION_MODE_SEARCH)
+    {
+      g_signal_emit_by_name (impl, "file-activated");
+      return;
+    }
+
   if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (impl->sort_model), &iter, path))
     return;
 
@@ -8427,6 +9213,15 @@ list_icon_data_func (GtkTreeViewColumn *tree_column,
   const GtkFileInfo *info; 
   gboolean sensitive = TRUE;
 
+  if (impl->operation_mode == OPERATION_MODE_SEARCH)
+    {
+      g_object_set (cell,
+                   "pixbuf", NULL,
+                   "sensitive", TRUE,
+                   NULL);
+      return;
+    }
+  
   profile_start ("start", NULL);
   
   info = get_list_file_info (impl, iter);
@@ -8476,13 +9271,33 @@ list_name_data_func (GtkTreeViewColumn *tree_column,
                     gpointer           data)
 {
   GtkFileChooserDefault *impl = data;
-  const GtkFileInfo *info = get_list_file_info (impl, iter);
-  gboolean sensitive = TRUE;
+  const GtkFileInfo *info;
+  gboolean sensitive;
+
+  if (impl->operation_mode == OPERATION_MODE_SEARCH)
+    {
+      char *display_name;
+
+      gtk_tree_model_get (GTK_TREE_MODEL (impl->search_model), iter,
+                         SEARCH_MODEL_COL_DISPLAY_NAME, &display_name,
+                         -1);
+      g_object_set (cell,
+                   "text", display_name,
+                   "sensitive", TRUE,
+                   "ellipsize", PANGO_ELLIPSIZE_START,
+                   NULL);
+      return;
+    }
+
+  info = get_list_file_info (impl, iter);
+  sensitive = TRUE;
 
   if (!info)
     {
       g_object_set (cell,
                    "text", _("Type name of new folder"),
+                   "sensitive", TRUE,
+                   "ellipsize", PANGO_ELLIPSIZE_NONE,
                    NULL);
 
       return;
@@ -8498,6 +9313,7 @@ list_name_data_func (GtkTreeViewColumn *tree_column,
   g_object_set (cell,
                "text", gtk_file_info_get_display_name (info),
                "sensitive", sensitive,
+               "ellipsize", PANGO_ELLIPSIZE_END,
                NULL);
 }
 
@@ -8559,7 +9375,6 @@ list_mtime_data_func (GtkTreeViewColumn *tree_column,
                      gpointer           data)
 {
   GtkFileChooserDefault *impl;
-  const GtkFileInfo *info;
   GtkFileTime time_mtime;
   GDate mtime, now;
   int days_diff;
@@ -8568,17 +9383,35 @@ list_mtime_data_func (GtkTreeViewColumn *tree_column,
 
   impl = data;
 
-  info = get_list_file_info (impl, iter);
-  if (!info)
+  if (impl->operation_mode == OPERATION_MODE_SEARCH)
     {
-      g_object_set (cell,
-                   "text", "",
-                   "sensitive", TRUE,
-                   NULL);
-      return;
+      struct stat *statbuf;
+
+      gtk_tree_model_get (GTK_TREE_MODEL (impl->search_model), iter,
+                         SEARCH_MODEL_COL_STAT, &statbuf,
+                         -1);
+      time_mtime = statbuf->st_mtime;
     }
+  else
+    {
+      const GtkFileInfo *info;
+
+      info = get_list_file_info (impl, iter);
+      if (!info)
+       {
+         g_object_set (cell,
+                       "text", "",
+                       "sensitive", TRUE,
+                       NULL);
+         return;
+       }
 
-  time_mtime = gtk_file_info_get_modification_time (info);
+      time_mtime = gtk_file_info_get_modification_time (info);
+
+      if (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ||
+         impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
+       sensitive = gtk_file_info_get_is_folder (info);
+    }
 
   if (time_mtime == 0)
     strcpy (buf, _("Unknown"));
@@ -8609,10 +9442,6 @@ list_mtime_data_func (GtkTreeViewColumn *tree_column,
        }
     }
 
-  if (impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER ||
-      impl->action == GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER)
-    sensitive = gtk_file_info_get_is_folder (info);
-
   g_object_set (cell,
                "text", buf,
                "sensitive", sensitive,
@@ -8638,7 +9467,24 @@ location_set_user_text (GtkFileChooserDefault *impl,
 static void
 location_popup_handler (GtkFileChooserDefault *impl,
                        const gchar           *path)
-{
+{ 
+  if (impl->operation_mode == OPERATION_MODE_SEARCH)
+    {
+      GtkWidget *widget_to_focus;
+      
+      /* This will give us the location widgets back */
+      search_switch_to_browse_mode (impl);
+      if (impl->current_folder)
+        change_folder_and_display_error (impl, impl->current_folder, FALSE);
+
+      if (impl->location_mode == LOCATION_MODE_PATH_BAR)
+        widget_to_focus = impl->browse_files_tree_view;
+      else
+        widget_to_focus = impl->location_entry;
+
+      gtk_widget_grab_focus (widget_to_focus);
+      return; 
+    }
   if (impl->action == GTK_FILE_CHOOSER_ACTION_OPEN
       || impl->action == GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER)
     {
@@ -8707,6 +9553,7 @@ switch_to_shortcut (GtkFileChooserDefault *impl,
     g_assert_not_reached ();
 
   shortcuts_activate_iter (impl, &iter);
+  focus_browse_tree_view_if_possible (impl);
 }
 
 /* Handler for the "home-folder" keybinding signal */
@@ -8758,26 +9605,26 @@ show_hidden_handler (GtkFileChooserDefault *impl)
 /* Drag and drop interfaces */
 
 static void
-_shortcuts_model_filter_class_init (ShortcutsModelFilterClass *class)
+_shortcuts_pane_model_filter_class_init (ShortcutsPaneModelFilterClass *class)
 {
 }
 
 static void
-_shortcuts_model_filter_init (ShortcutsModelFilter *model)
+_shortcuts_pane_model_filter_init (ShortcutsPaneModelFilter *model)
 {
   model->impl = NULL;
 }
 
 /* GtkTreeDragSource::row_draggable implementation for the shortcuts filter model */
 static gboolean
-shortcuts_model_filter_row_draggable (GtkTreeDragSource *drag_source,
-                                     GtkTreePath       *path)
+shortcuts_pane_model_filter_row_draggable (GtkTreeDragSource *drag_source,
+                                          GtkTreePath       *path)
 {
-  ShortcutsModelFilter *model;
+  ShortcutsPaneModelFilter *model;
   int pos;
   int bookmarks_pos;
 
-  model = SHORTCUTS_MODEL_FILTER (drag_source);
+  model = SHORTCUTS_PANE_MODEL_FILTER (drag_source);
 
   pos = *gtk_tree_path_get_indices (path);
   bookmarks_pos = shortcuts_get_index (model->impl, SHORTCUTS_BOOKMARKS);
@@ -8787,13 +9634,13 @@ shortcuts_model_filter_row_draggable (GtkTreeDragSource *drag_source,
 
 /* GtkTreeDragSource::drag_data_get implementation for the shortcuts filter model */
 static gboolean
-shortcuts_model_filter_drag_data_get (GtkTreeDragSource *drag_source,
-                                     GtkTreePath       *path,
-                                     GtkSelectionData  *selection_data)
+shortcuts_pane_model_filter_drag_data_get (GtkTreeDragSource *drag_source,
+                                          GtkTreePath       *path,
+                                          GtkSelectionData  *selection_data)
 {
-  ShortcutsModelFilter *model;
+  ShortcutsPaneModelFilter *model;
 
-  model = SHORTCUTS_MODEL_FILTER (drag_source);
+  model = SHORTCUTS_PANE_MODEL_FILTER (drag_source);
 
   /* FIXME */
 
@@ -8802,30 +9649,30 @@ shortcuts_model_filter_drag_data_get (GtkTreeDragSource *drag_source,
 
 /* Fill the GtkTreeDragSourceIface vtable */
 static void
-shortcuts_model_filter_drag_source_iface_init (GtkTreeDragSourceIface *iface)
+shortcuts_pane_model_filter_drag_source_iface_init (GtkTreeDragSourceIface *iface)
 {
-  iface->row_draggable = shortcuts_model_filter_row_draggable;
-  iface->drag_data_get = shortcuts_model_filter_drag_data_get;
+  iface->row_draggable = shortcuts_pane_model_filter_row_draggable;
+  iface->drag_data_get = shortcuts_pane_model_filter_drag_data_get;
 }
 
 #if 0
 /* Fill the GtkTreeDragDestIface vtable */
 static void
-shortcuts_model_filter_drag_dest_iface_init (GtkTreeDragDestIface *iface)
+shortcuts_pane_model_filter_drag_dest_iface_init (GtkTreeDragDestIface *iface)
 {
-  iface->drag_data_received = shortcuts_model_filter_drag_data_received;
-  iface->row_drop_possible = shortcuts_model_filter_row_drop_possible;
+  iface->drag_data_received = shortcuts_pane_model_filter_drag_data_received;
+  iface->row_drop_possible = shortcuts_pane_model_filter_row_drop_possible;
 }
 #endif
 
 static GtkTreeModel *
-shortcuts_model_filter_new (GtkFileChooserDefault *impl,
-                           GtkTreeModel          *child_model,
-                           GtkTreePath           *root)
+shortcuts_pane_model_filter_new (GtkFileChooserDefault *impl,
+                                GtkTreeModel          *child_model,
+                                GtkTreePath           *root)
 {
-  ShortcutsModelFilter *model;
+  ShortcutsPaneModelFilter *model;
 
-  model = g_object_new (SHORTCUTS_MODEL_FILTER_TYPE,
+  model = g_object_new (SHORTCUTS_PANE_MODEL_FILTER_TYPE,
                        "child-model", child_model,
                        "virtual-root", root,
                        NULL);
index 24f57e05912ee2941e40a79bbabbbedcd286b597..3a7416ccdd44ab5eff05e3871ce333841c673b6d 100644 (file)
@@ -25,6 +25,8 @@
 #include "gtkfilesystem.h"
 #include "gtkfilesystemmodel.h"
 #include "gtkliststore.h"
+#include "gtksearchengine.h"
+#include "gtkquery.h"
 #include "gtktooltips.h"
 #include "gtktreemodelsort.h"
 #include "gtktreestore.h"
@@ -147,6 +149,11 @@ typedef enum {
   LOCATION_MODE_FILENAME_ENTRY
 } LocationMode;
 
+typedef enum {
+  OPERATION_MODE_BROWSE,
+  OPERATION_MODE_SEARCH
+} OperationMode;
+
 struct _GtkFileChooserDefault
 {
   GtkVBox parent_instance;
@@ -175,11 +182,19 @@ struct _GtkFileChooserDefault
   GtkWidget *browse_files_popup_menu_add_shortcut_item;
   GtkWidget *browse_files_popup_menu_hidden_files_item;
   GtkWidget *browse_new_folder_button;
+  GtkWidget *browse_path_bar_hbox;
   GtkWidget *browse_path_bar;
 
   GtkFileSystemModel *browse_files_model;
   char *browse_files_last_selected_name;
 
+  /* Widgets for searching */
+  GtkWidget *search_hbox;
+  GtkWidget *search_entry;
+  GtkSearchEngine *search_engine;
+  GtkQuery *search_query;
+  GtkListStore *search_model;
+
   GtkWidget *filter_combo_hbox;
   GtkWidget *filter_combo;
   GtkWidget *preview_box;
@@ -195,7 +210,16 @@ struct _GtkFileChooserDefault
   LocationMode location_mode;
 
   GtkListStore *shortcuts_model;
-  GtkTreeModel *shortcuts_filter_model;
+
+  /* Filter for the shortcuts pane.  We filter out the "current folder" row and
+   * the separator that we use for the "Save in folder" combo.
+   */
+  GtkTreeModel *shortcuts_pane_filter_model;
+  
+  /* Filter for the "Save in folder" combo.  We filter out the Search row and
+   * its separator.
+   */
+  GtkTreeModel *shortcuts_combo_filter_model;
 
   GtkTreeModelSort *sort_model;
 
@@ -215,6 +239,8 @@ struct _GtkFileChooserDefault
   ReloadState reload_state;
   guint load_timeout_id;
 
+  OperationMode operation_mode;
+
   GSList *pending_select_paths;
 
   GtkFileFilter *current_filter;
@@ -222,9 +248,6 @@ struct _GtkFileChooserDefault
 
   GtkTooltips *tooltips;
 
-  gboolean has_home;
-  gboolean has_desktop;
-
   int num_volumes;
   int num_shortcuts;
   int num_bookmarks;
@@ -239,6 +262,7 @@ struct _GtkFileChooserDefault
 
   GtkTreeViewColumn *list_name_column;
   GtkCellRenderer *list_name_renderer;
+  GtkTreeViewColumn *list_mtime_column;
 
   GSource *edited_idle;
   char *edited_new_text;
@@ -266,6 +290,9 @@ struct _GtkFileChooserDefault
   guint changing_folder : 1;
   guint shortcuts_current_folder_active : 1;
   guint expand_folders : 1;
+  guint has_home : 1;
+  guint has_desktop : 1;
+  guint has_search : 1;
 
 #if 0
   guint shortcuts_drag_outside : 1;
diff --git a/gtk/gtkquery.c b/gtk/gtkquery.c
new file mode 100644 (file)
index 0000000..274a0a6
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *
+ * Author: Anders Carlsson <andersca@imendio.com>
+ *
+ * Based on nautilus-query.c
+ */
+
+#include <config.h>
+#include <string.h>
+
+#include "gtkquery.h"
+
+struct _GtkQueryPrivate 
+{
+  gchar *text;
+  gchar *location_uri;
+  GList *mime_types;
+};
+
+G_DEFINE_TYPE (GtkQuery, _gtk_query, G_TYPE_OBJECT);
+
+static void
+finalize (GObject *object)
+{
+  GtkQuery *query;
+  
+  query = GTK_QUERY (object);
+  
+  g_free (query->priv->text);
+
+  G_OBJECT_CLASS (_gtk_query_parent_class)->finalize (object);
+}
+
+static void
+_gtk_query_class_init (GtkQueryClass *class)
+{
+  GObjectClass *gobject_class;
+  
+  gobject_class = G_OBJECT_CLASS (class);
+  gobject_class->finalize = finalize;
+
+  g_type_class_add_private (gobject_class, sizeof (GtkQueryPrivate));  
+}
+
+static void
+_gtk_query_init (GtkQuery *query)
+{
+  query->priv = G_TYPE_INSTANCE_GET_PRIVATE (query, GTK_TYPE_QUERY, GtkQueryPrivate);
+}
+
+GtkQuery *
+_gtk_query_new (void)
+{
+  return g_object_new (GTK_TYPE_QUERY,  NULL);
+}
+
+
+gchar *
+_gtk_query_get_text (GtkQuery *query)
+{
+  return g_strdup (query->priv->text);
+}
+
+void 
+_gtk_query_set_text (GtkQuery    *query, 
+                   const gchar *text)
+{
+  g_free (query->priv->text);
+  query->priv->text = g_strdup (text);
+}
+
+gchar *
+_gtk_query_get_location (GtkQuery *query)
+{
+  return g_strdup (query->priv->location_uri);
+}
+       
+void
+_gtk_query_set_location (GtkQuery    *query, 
+                       const gchar *uri)
+{
+  g_free (query->priv->location_uri);
+  query->priv->location_uri = g_strdup (uri);
+}
+
+GList *
+_gtk_query_get_mime_types (GtkQuery *query)
+{
+  GList *list, *l;
+  gchar *mime_type;
+
+  list = NULL;
+  for (l = query->priv->mime_types; l; l = l->next)
+    {
+      mime_type = (gchar*)l->data;
+      list = g_list_prepend (list, g_strdup (mime_type));
+    }
+
+  return list;
+}
+
+void
+_gtk_query_set_mime_types (GtkQuery *query, 
+                          GList    *mime_types)
+{
+  GList *l;
+  gchar *mime_type;
+
+  g_list_foreach (query->priv->mime_types, (GFunc)g_free, NULL);
+  g_list_free (query->priv->mime_types);
+  query->priv->mime_types = NULL;
+
+  for (l = mime_types; l; l = l->next)
+    {
+      mime_type = (gchar*)l->data;
+      query->priv->mime_types = g_list_prepend (query->priv->mime_types, g_strdup (mime_type));
+    }
+}
+
+void
+_gtk_query_add_mime_type (GtkQuery    *query, 
+                         const gchar *mime_type)
+{
+  query->priv->mime_types = g_list_prepend (query->priv->mime_types,
+                                           g_strdup (mime_type));
+}
+
diff --git a/gtk/gtkquery.h b/gtk/gtkquery.h
new file mode 100644 (file)
index 0000000..a6e6553
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *
+ * Author: Anders Carlsson <andersca@imendio.com>
+ *
+ * Based on nautilus-query.h
+ */
+
+#ifndef __GTK_QUERY_H__
+#define __GTK_QUERY_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_QUERY         (_gtk_query_get_type ())
+#define GTK_QUERY(obj)         (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_QUERY, GtkQuery))
+#define GTK_QUERY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_QUERY, GtkQueryClass))
+#define GTK_IS_QUERY(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_QUERY))
+#define GTK_IS_QUERY_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_QUERY))
+#define GTK_QUERY_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_QUERY, GtkQueryClass))
+
+typedef struct _GtkQuery GtkQuery;
+typedef struct _GtkQueryClass GtkQueryClass;
+typedef struct _GtkQueryPrivate GtkQueryPrivate;
+
+struct _GtkQuery 
+{
+  GObject parent;
+
+  GtkQueryPrivate *priv;
+};
+
+struct _GtkQueryClass
+{
+  GObjectClass parent_class;
+};
+
+GType     _gtk_query_get_type       (void);
+gboolean  _gtk_query_enabled        (void);
+
+GtkQuery* _gtk_query_new            (void);
+
+gchar*    _gtk_query_get_text       (GtkQuery    *query);
+void      _gtk_query_set_text       (GtkQuery    *query, 
+                                    const gchar *text);
+
+gchar*    _gtk_query_get_location   (GtkQuery    *query);
+void      _gtk_query_set_location   (GtkQuery    *query, 
+                                    const gchar *uri);
+
+GList*    _gtk_query_get_mime_types (GtkQuery    *query);
+void      _gtk_query_set_mime_types (GtkQuery    *query, 
+                                    GList       *mime_types);
+void      _gtk_query_add_mime_type  (GtkQuery    *query, 
+                                    const gchar *mime_type);
+
+G_END_DECLS
+
+#endif /* __GTK_QUERY_H__ */
diff --git a/gtk/gtksearchengine.c b/gtk/gtksearchengine.c
new file mode 100644 (file)
index 0000000..196e944
--- /dev/null
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *
+ * Author: Anders Carlsson <andersca@imendio.com>
+ *
+ * Based on nautilus-search-engine.c
+ */
+
+#include <config.h>
+#include "gtksearchengine.h"
+#include "gtksearchenginebeagle.h"
+#include "gtksearchenginesimple.h"
+#include "gtksearchenginetracker.h"
+
+#define HAVE_BEAGLE  1 
+#define HAVE_TRACKER 1
+
+enum 
+{
+  HITS_ADDED,
+  HITS_SUBTRACTED,
+  FINISHED,
+  ERROR,
+  LAST_SIGNAL
+}; 
+
+static guint signals[LAST_SIGNAL];
+
+G_DEFINE_ABSTRACT_TYPE (GtkSearchEngine, _gtk_search_engine, G_TYPE_OBJECT);
+
+static void
+finalize (GObject *object)
+{
+  G_OBJECT_CLASS (_gtk_search_engine_parent_class)->finalize (object);
+}
+
+static void
+_gtk_search_engine_class_init (GtkSearchEngineClass *class)
+{
+  GObjectClass *gobject_class;
+  
+  gobject_class = G_OBJECT_CLASS (class);
+  gobject_class->finalize = finalize;
+  
+  signals[HITS_ADDED] =
+    g_signal_new ("hits-added",
+                 G_TYPE_FROM_CLASS (class),
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET (GtkSearchEngineClass, hits_added),
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__POINTER,
+                 G_TYPE_NONE, 1,
+                 G_TYPE_POINTER);
+  
+  signals[HITS_SUBTRACTED] =
+    g_signal_new ("hits-subtracted",
+                 G_TYPE_FROM_CLASS (class),
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET (GtkSearchEngineClass, hits_subtracted),
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__POINTER,
+                 G_TYPE_NONE, 1,
+                 G_TYPE_POINTER);
+  
+  signals[FINISHED] =
+    g_signal_new ("finished",
+                 G_TYPE_FROM_CLASS (class),
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET (GtkSearchEngineClass, finished),
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__VOID,
+                 G_TYPE_NONE, 0);
+  
+  signals[ERROR] =
+    g_signal_new ("error",
+                 G_TYPE_FROM_CLASS (class),
+                 G_SIGNAL_RUN_LAST,
+                 G_STRUCT_OFFSET (GtkSearchEngineClass, error),
+                 NULL, NULL,
+                 g_cclosure_marshal_VOID__STRING,
+                 G_TYPE_NONE, 1,
+                 G_TYPE_STRING);  
+}
+
+static void
+_gtk_search_engine_init (GtkSearchEngine *engine)
+{
+}
+
+GtkSearchEngine *
+_gtk_search_engine_new (void)
+{
+  GtkSearchEngine *engine;
+       
+#ifdef HAVE_TRACKER
+  engine = _gtk_search_engine_tracker_new ();
+  if (engine)
+    return engine;
+#endif
+  
+#ifdef HAVE_BEAGLE
+  engine = _gtk_search_engine_beagle_new ();
+  if (engine)
+    return engine;
+#endif
+
+  engine = _gtk_search_engine_simple_new ();
+  return engine;
+}
+
+void
+_gtk_search_engine_set_query (GtkSearchEngine *engine, 
+                             GtkQuery        *query)
+{
+  g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine));
+  g_return_if_fail (GTK_SEARCH_ENGINE_GET_CLASS (engine)->set_query != NULL);
+  
+  GTK_SEARCH_ENGINE_GET_CLASS (engine)->set_query (engine, query);
+}
+
+void
+_gtk_search_engine_start (GtkSearchEngine *engine)
+{
+  g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine));
+  g_return_if_fail (GTK_SEARCH_ENGINE_GET_CLASS (engine)->start != NULL);
+  
+  GTK_SEARCH_ENGINE_GET_CLASS (engine)->start (engine);
+}
+
+void
+_gtk_search_engine_stop (GtkSearchEngine *engine)
+{
+  g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine));
+  g_return_if_fail (GTK_SEARCH_ENGINE_GET_CLASS (engine)->stop != NULL);
+  
+  GTK_SEARCH_ENGINE_GET_CLASS (engine)->stop (engine);
+}
+
+gboolean
+_gtk_search_engine_is_indexed (GtkSearchEngine *engine)
+{
+  g_return_val_if_fail (GTK_IS_SEARCH_ENGINE (engine), FALSE);
+  g_return_val_if_fail (GTK_SEARCH_ENGINE_GET_CLASS (engine)->is_indexed != NULL, FALSE);
+  
+  return GTK_SEARCH_ENGINE_GET_CLASS (engine)->is_indexed (engine);
+}
+
+void          
+_gtk_search_engine_hits_added (GtkSearchEngine *engine, 
+                              GList           *hits)
+{
+  g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine));
+  
+  g_signal_emit (engine, signals[HITS_ADDED], 0, hits);
+}
+
+
+void          
+_gtk_search_engine_hits_subtracted (GtkSearchEngine *engine, 
+                                   GList           *hits)
+{
+  g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine));
+  
+  g_signal_emit (engine, signals[HITS_SUBTRACTED], 0, hits);
+}
+
+
+void          
+_gtk_search_engine_finished (GtkSearchEngine *engine)
+{
+  g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine));
+  
+  g_signal_emit (engine, signals[FINISHED], 0);
+}
+
+void
+_gtk_search_engine_error (GtkSearchEngine *engine, 
+                         const gchar     *error_message)
+{
+  g_return_if_fail (GTK_IS_SEARCH_ENGINE (engine));
+  
+  g_signal_emit (engine, signals[ERROR], 0, error_message);
+}
diff --git a/gtk/gtksearchengine.h b/gtk/gtksearchengine.h
new file mode 100644 (file)
index 0000000..3b0a34c
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *
+ * Author: Anders Carlsson <andersca@imendio.com> 
+ *
+ * Based on nautilus-search-engine.h
+ */
+
+#ifndef __GTK_SEARCH_ENGINE_H__
+#define __GTK_SEARCH_ENGINE_H__
+
+#include <glib-object.h>
+#include "gtkquery.h"
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SEARCH_ENGINE         (_gtk_search_engine_get_type ())
+#define GTK_SEARCH_ENGINE(obj)         (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SEARCH_ENGINE, GtkSearchEngine))
+#define GTK_SEARCH_ENGINE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SEARCH_ENGINE, GtkSearchEngineClass))
+#define GTK_IS_SEARCH_ENGINE(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SEARCH_ENGINE))
+#define GTK_IS_SEARCH_ENGINE_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SEARCH_ENGINE))
+#define GTK_SEARCH_ENGINE_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SEARCH_ENGINE, GtkSearchEngineClass))
+
+typedef struct _GtkSearchEngine GtkSearchEngine;
+typedef struct _GtkSearchEngineClass GtkSearchEngineClass;
+typedef struct _GtkSearchEnginePrivate GtkSearchEnginePrivate;
+
+struct _GtkSearchEngine 
+{
+  GObject parent;
+
+  GtkSearchEnginePrivate *priv;
+};
+
+struct _GtkSearchEngineClass 
+{
+  GObjectClass parent_class;
+  
+  /* VTable */
+  void     (*set_query)       (GtkSearchEngine *engine, 
+                              GtkQuery        *query);
+  void     (*start)           (GtkSearchEngine *engine);
+  void     (*stop)            (GtkSearchEngine *engine);
+  gboolean (*is_indexed)      (GtkSearchEngine *engine);
+  
+  /* Signals */
+  void     (*hits_added)      (GtkSearchEngine *engine, 
+                              GList           *hits);
+  void     (*hits_subtracted) (GtkSearchEngine *engine, 
+                              GList           *hits);
+  void     (*finished)        (GtkSearchEngine *engine);
+  void     (*error)           (GtkSearchEngine *engine, 
+                              const gchar     *error_message);
+};
+
+GType            _gtk_search_engine_get_type        (void);
+gboolean         _gtk_search_engine_enabled         (void);
+
+GtkSearchEngine* _gtk_search_engine_new             (void);
+
+void             _gtk_search_engine_set_query       (GtkSearchEngine *engine, 
+                                                     GtkQuery        *query);
+void            _gtk_search_engine_start           (GtkSearchEngine *engine);
+void            _gtk_search_engine_stop            (GtkSearchEngine *engine);
+gboolean         _gtk_search_engine_is_indexed      (GtkSearchEngine *engine);
+
+void            _gtk_search_engine_hits_added      (GtkSearchEngine *engine, 
+                                                    GList           *hits);
+void            _gtk_search_engine_hits_subtracted (GtkSearchEngine *engine, 
+                                                    GList           *hits);
+void            _gtk_search_engine_finished        (GtkSearchEngine *engine);
+void            _gtk_search_engine_error           (GtkSearchEngine *engine, 
+                                                    const gchar     *error_message);
+
+G_END_DECLS
+
+#endif /* __GTK_SEARCH_ENGINE_H__ */
diff --git a/gtk/gtksearchenginebeagle.c b/gtk/gtksearchenginebeagle.c
new file mode 100644 (file)
index 0000000..f85ac06
--- /dev/null
@@ -0,0 +1,422 @@
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *
+ * Author: Anders Carlsson <andersca@imendio.com>
+ *
+ * Based on nautilus-search-engine-beagle.c
+ */
+
+#include <config.h>
+#include <gmodule.h>
+#include "gtksearchenginebeagle.h"
+#if 0
+#include <beagle/beagle.h>
+#endif
+
+/* We dlopen() all the following from libbeagle at runtime */
+
+typedef struct _BeagleHit BeagleHit;
+typedef struct _BeagleQuery BeagleQuery;
+typedef struct _BeagleClient BeagleClient;
+typedef struct _BeagleRequest BeagleRequest;
+typedef struct _BeagleFinishedResponse BeagleFinishedResponse;
+typedef struct _BeagleHitsAddedResponse BeagleHitsAddedResponse;
+typedef struct _BeagleHitsSubtractedResponse BeagleHitsSubtractedResponse;
+typedef struct _BeagleQueryPartProperty BeagleQueryPartProperty;
+typedef struct _BeagleQueryPart BeagleQueryPart;
+
+#define BEAGLE_HIT(x) ((BeagleHit *)(x))
+#define BEAGLE_REQUEST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), beagle_request_get_type(), BeagleRequest))
+#define BEAGLE_QUERY_PART(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), beagle_query_part_get_type(), BeagleQueryPart))
+
+typedef enum 
+{
+  BEAGLE_QUERY_PART_LOGIC_REQUIRED   = 1,
+  BEAGLE_QUERY_PART_LOGIC_PROHIBITED = 2
+} BeagleQueryPartLogic;
+
+typedef enum 
+  {
+    BEAGLE_PROPERTY_TYPE_UNKNOWN = 0,
+    BEAGLE_PROPERTY_TYPE_TEXT    = 1,
+    BEAGLE_PROPERTY_TYPE_KEYWORD = 2,
+    BEAGLE_PROPERTY_TYPE_DATE    = 3,
+    BEAGLE_PROPERTY_TYPE_LAST    = 4
+} BeaglePropertyType;
+
+/* *static* wrapper function pointers */
+static gboolean (*beagle_client_send_request_async) (BeagleClient  *client,
+                                                    BeagleRequest  *request,
+                                                    GError        **err) = NULL;
+static G_CONST_RETURN char *(*beagle_hit_get_uri) (BeagleHit *hit) = NULL;
+static GSList *(*beagle_hits_added_response_get_hits) (BeagleHitsAddedResponse *response) = NULL;
+static GSList *(*beagle_hits_subtracted_response_get_uris) (BeagleHitsSubtractedResponse *response) = NULL;
+static BeagleQuery *(*beagle_query_new) (void) = NULL;
+static void (*beagle_query_add_text) (BeagleQuery     *query,
+                                     const char      *str) = NULL;
+static void (*beagle_query_add_hit_type) (BeagleQuery *query,
+                                         const char  *hit_type) = NULL;
+static void (*beagle_query_add_mime_type) (BeagleQuery *query,
+                                         const char  *mime_type) = NULL;
+static void (*beagle_query_set_max_hits) (BeagleQuery *query,
+                                         gint         max_hits) = NULL;
+static BeagleQueryPartProperty *(*beagle_query_part_property_new) (void) = NULL;
+static void (*beagle_query_part_set_logic) (BeagleQueryPart      *part, 
+                                           BeagleQueryPartLogic  logic) = NULL;
+static void (*beagle_query_part_property_set_key) (BeagleQueryPartProperty *part, 
+                                                  const char              *key) = NULL;
+static void (*beagle_query_part_property_set_value) (BeagleQueryPartProperty *part, 
+                                                    const char *             value) = NULL;
+static void (*beagle_query_part_property_set_property_type) (BeagleQueryPartProperty *part, 
+                                                            BeaglePropertyType       prop_type) = NULL;
+static void (*beagle_query_add_part) (BeagleQuery     *query, 
+                                     BeagleQueryPart *part) = NULL;
+static GType (*beagle_request_get_type) (void) = NULL;
+static GType (*beagle_query_part_get_type) (void) = NULL;
+static gboolean (*beagle_util_daemon_is_running) (void) = NULL;
+static BeagleClient *(*beagle_client_new) (const char *client_name) = NULL;
+
+static struct BeagleDlMapping
+{
+  const char *fn_name;
+  gpointer *fn_ptr_ref;
+} beagle_dl_mapping[] =
+{
+#define MAP(a) { #a, (gpointer *)&a }
+  MAP (beagle_client_send_request_async),
+  MAP (beagle_hit_get_uri),
+  MAP (beagle_hits_added_response_get_hits),
+  MAP (beagle_hits_subtracted_response_get_uris),
+  MAP (beagle_query_new),
+  MAP (beagle_query_add_text),
+  MAP (beagle_query_add_hit_type),
+  MAP (beagle_query_add_mime_type),
+  MAP (beagle_query_set_max_hits),
+  MAP (beagle_query_part_property_new),
+  MAP (beagle_query_part_set_logic),
+  MAP (beagle_query_part_property_set_key),
+  MAP (beagle_query_part_property_set_value),
+  MAP (beagle_query_part_property_set_property_type),
+  MAP (beagle_query_add_part),
+  MAP (beagle_request_get_type),
+  MAP (beagle_query_part_get_type),
+  MAP (beagle_util_daemon_is_running),
+  MAP (beagle_client_new)
+#undef MAP
+};
+
+static void 
+open_libbeagle (void)
+{
+  static gboolean done = FALSE;
+
+  if (!done)
+    {
+      int i;
+      GModule *beagle;
+      
+      done = TRUE;
+      beagle = g_module_open ("libbeagle.so.0", G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
+      if (!beagle)
+       return;
+      
+      for (i = 0; i < G_N_ELEMENTS (beagle_dl_mapping); i++)
+       {
+         if (!g_module_symbol (beagle, beagle_dl_mapping[i].fn_name,
+                               beagle_dl_mapping[i].fn_ptr_ref))
+           {
+             g_warning ("Missing symbol '%s' in libbeagle\n",
+                        beagle_dl_mapping[i].fn_name);
+             g_module_close (beagle);
+
+             for (i = 0; i < G_N_ELEMENTS (beagle_dl_mapping); i++)
+               beagle_dl_mapping[i].fn_ptr_ref = NULL;
+
+             return;
+           }
+       }
+    }
+}
+
+
+struct _GtkSearchEngineBeaglePrivate 
+{
+  BeagleClient *client;
+  GtkQuery *query;
+
+  BeagleQuery *current_query;
+  char *current_query_uri_prefix;
+  gboolean query_finished;
+};
+
+
+G_DEFINE_TYPE (GtkSearchEngineBeagle, _gtk_search_engine_beagle, GTK_TYPE_SEARCH_ENGINE);
+
+static void
+finalize (GObject *object)
+{
+  GtkSearchEngineBeagle *beagle;
+  
+  beagle = GTK_SEARCH_ENGINE_BEAGLE (object);
+  
+  if (beagle->priv->current_query) 
+    {
+      g_object_unref (beagle->priv->current_query);
+      beagle->priv->current_query = NULL;
+      g_free (beagle->priv->current_query_uri_prefix);
+      beagle->priv->current_query_uri_prefix = NULL;
+    }
+
+  if (beagle->priv->query) 
+    {
+      g_object_unref (beagle->priv->query);
+      beagle->priv->query = NULL;
+    }
+
+  if (beagle->priv->client) 
+    {
+      g_object_unref (beagle->priv->client);
+      beagle->priv->client = NULL;
+    }
+
+  G_OBJECT_CLASS (_gtk_search_engine_beagle_parent_class)->finalize (object);
+}
+
+static void
+beagle_hits_added (BeagleQuery             *query, 
+                  BeagleHitsAddedResponse *response, 
+                  GtkSearchEngineBeagle   *engine)
+{
+  GSList *hits, *list;
+  GList *hit_uris;
+  const gchar *uri;
+  
+  hit_uris = NULL;
+  
+  hits = beagle_hits_added_response_get_hits (response);
+  
+  for (list = hits; list != NULL; list = list->next) 
+    {
+      BeagleHit *hit = BEAGLE_HIT (list->data);
+      
+      uri = beagle_hit_get_uri (hit);
+      
+      if (engine->priv->current_query_uri_prefix &&
+         !g_str_has_prefix (uri, engine->priv->current_query_uri_prefix)) 
+       continue;
+               
+      hit_uris = g_list_prepend (hit_uris, (char *)uri);
+    }
+
+  _gtk_search_engine_hits_added (GTK_SEARCH_ENGINE (engine), hit_uris);
+  g_list_free (hit_uris);
+}
+
+static void
+beagle_hits_subtracted (BeagleQuery                  *query, 
+                       BeagleHitsSubtractedResponse *response, 
+                       GtkSearchEngineBeagle        *engine)
+{
+  GSList *uris, *list;
+  GList *hit_uris;
+  
+  hit_uris = NULL;
+  
+  uris = beagle_hits_subtracted_response_get_uris (response);
+  
+  for (list = uris; list != NULL; list = list->next) 
+    {
+      hit_uris = g_list_prepend (hit_uris, (char *)list->data);
+    }
+
+  _gtk_search_engine_hits_subtracted (GTK_SEARCH_ENGINE (engine), hit_uris);
+  g_list_free (hit_uris);
+}
+
+static void
+beagle_finished (BeagleQuery            *query, 
+                BeagleFinishedResponse *response,
+                GtkSearchEngineBeagle  *engine)
+{
+  /* For some reason we keep getting finished events,
+   * only emit finished once */
+  if (engine->priv->query_finished) 
+    return;
+  
+  engine->priv->query_finished = TRUE;
+  _gtk_search_engine_finished (GTK_SEARCH_ENGINE (engine));
+}
+
+static void
+beagle_error (BeagleQuery           *query,
+             GError                *error,
+             GtkSearchEngineBeagle *engine)
+{
+  _gtk_search_engine_error (GTK_SEARCH_ENGINE (engine), error->message);
+}
+
+static void
+gtk_search_engine_beagle_start (GtkSearchEngine *engine)
+{
+  GtkSearchEngineBeagle *beagle;
+  GError *error;
+  GList *mimetypes, *l;
+  gchar *text, *mimetype;
+
+  error = NULL;
+  beagle = GTK_SEARCH_ENGINE_BEAGLE (engine);
+  
+  g_return_if_fail (beagle->priv->query != NULL);
+
+  if (beagle->priv->current_query)
+    return;
+
+  beagle->priv->query_finished = FALSE;
+  beagle->priv->current_query = beagle_query_new ();
+  g_signal_connect (beagle->priv->current_query,
+                   "hits-added", G_CALLBACK (beagle_hits_added), engine);
+  g_signal_connect (beagle->priv->current_query,
+                   "hits-subtracted", G_CALLBACK (beagle_hits_subtracted), engine);
+  g_signal_connect (beagle->priv->current_query,
+                   "finished", G_CALLBACK (beagle_finished), engine);
+  g_signal_connect (beagle->priv->current_query,
+                   "error", G_CALLBACK (beagle_error), engine);
+  
+  /* We only want files */
+  beagle_query_add_hit_type (beagle->priv->current_query,
+                            "File");
+  beagle_query_set_max_hits (beagle->priv->current_query,
+                            1000);
+  
+  text = _gtk_query_get_text (beagle->priv->query);
+  beagle_query_add_text (beagle->priv->current_query,
+                        text);
+  
+  mimetypes = _gtk_query_get_mime_types (beagle->priv->query);
+  for (l = mimetypes; l != NULL; l = l->next) 
+    {
+      mimetype = l->data;
+      beagle_query_add_mime_type (beagle->priv->current_query, mimetype);
+    }
+
+  beagle->priv->current_query_uri_prefix = _gtk_query_get_location (beagle->priv->query);
+  
+  if (!beagle_client_send_request_async (beagle->priv->client,
+                                        BEAGLE_REQUEST (beagle->priv->current_query), &error)) 
+    {
+      _gtk_search_engine_error (engine, error->message);
+      g_error_free (error);
+    }
+
+  /* These must live during the lifetime of the query */
+  g_free (text);
+  g_list_foreach (mimetypes, (GFunc)g_free, NULL);
+  g_list_free (mimetypes);
+}
+
+static void
+gtk_search_engine_beagle_stop (GtkSearchEngine *engine)
+{
+  GtkSearchEngineBeagle *beagle;
+  
+  beagle = GTK_SEARCH_ENGINE_BEAGLE (engine);
+  
+  if (beagle->priv->current_query) 
+    {
+      g_object_unref (beagle->priv->current_query);
+      beagle->priv->current_query = NULL;
+      g_free (beagle->priv->current_query_uri_prefix);
+      beagle->priv->current_query_uri_prefix = NULL;
+    }
+}
+
+static gboolean
+gtk_search_engine_beagle_is_indexed (GtkSearchEngine *engine)
+{
+  return TRUE;
+}
+
+static void
+gtk_search_engine_beagle_set_query (GtkSearchEngine *engine, 
+                                   GtkQuery        *query)
+{
+  GtkSearchEngineBeagle *beagle;
+  
+  beagle = GTK_SEARCH_ENGINE_BEAGLE (engine);
+  
+  if (query)
+    g_object_ref (query);
+
+  if (beagle->priv->query)
+    g_object_unref (beagle->priv->query);
+
+  beagle->priv->query = query;
+}
+
+static void
+_gtk_search_engine_beagle_class_init (GtkSearchEngineBeagleClass *class)
+{
+  GObjectClass *gobject_class;
+  GtkSearchEngineClass *engine_class;
+  
+  gobject_class = G_OBJECT_CLASS (class);
+  gobject_class->finalize = finalize;
+  
+  engine_class = GTK_SEARCH_ENGINE_CLASS (class);
+  engine_class->set_query = gtk_search_engine_beagle_set_query;
+  engine_class->start = gtk_search_engine_beagle_start;
+  engine_class->stop = gtk_search_engine_beagle_stop;
+  engine_class->is_indexed = gtk_search_engine_beagle_is_indexed;
+
+  g_type_class_add_private (gobject_class, sizeof (GtkSearchEngineBeaglePrivate));
+}
+
+static void
+_gtk_search_engine_beagle_init (GtkSearchEngineBeagle *engine)
+{
+  engine->priv = G_TYPE_INSTANCE_GET_PRIVATE (engine, GTK_TYPE_SEARCH_ENGINE_BEAGLE, GtkSearchEngineBeaglePrivate);
+}
+
+
+GtkSearchEngine *
+_gtk_search_engine_beagle_new (void)
+{
+  GtkSearchEngineBeagle *engine;
+  BeagleClient *client;
+
+  open_libbeagle ();
+
+  if (!beagle_util_daemon_is_running)
+    return NULL;
+
+  /* check whether daemon is running as beagle_client_new
+   * doesn't fail when a stale socket file exists */
+  if (!beagle_util_daemon_is_running ()) 
+      return NULL;
+
+  client = beagle_client_new (NULL);
+  
+  if (client == NULL)
+    return NULL;
+       
+  engine = g_object_new (GTK_TYPE_SEARCH_ENGINE_BEAGLE, NULL);
+  
+  engine->priv->client = client;
+  
+  return GTK_SEARCH_ENGINE (engine);
+}
diff --git a/gtk/gtksearchenginebeagle.h b/gtk/gtksearchenginebeagle.h
new file mode 100644 (file)
index 0000000..e768ff1
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2005 Novell, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *
+ * Author: Anders Carlsson <andersca@imendio.com>
+ *
+ * Based on nautilus-search-engine-beagle.h
+ */
+
+#ifndef __GTK_SEARCH_ENGINE_BEAGLE_H__
+#define __GTK_SEARCH_ENGINE_BEAGLE_H__
+
+#include "gtksearchengine.h"
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SEARCH_ENGINE_BEAGLE          (_gtk_search_engine_beagle_get_type ())
+#define GTK_SEARCH_ENGINE_BEAGLE(obj)          (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SEARCH_ENGINE_BEAGLE, GtkSearchEngineBeagle))
+#define GTK_SEARCH_ENGINE_BEAGLE_CLASS(klass)  (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SEARCH_ENGINE_BEAGLE, GtkSearchEngineBeagleClass))
+#define GTK_IS_SEARCH_ENGINE_BEAGLE(obj)               (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SEARCH_ENGINE_BEAGLE))
+#define GTK_IS_SEARCH_ENGINE_BEAGLE_CLASS(klass)       (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SEARCH_ENGINE_BEAGLE))
+#define GTK_SEARCH_ENGINE_BEAGLE_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SEARCH_ENGINE_BEAGLE, GtkSearchEngineBeagleClass))
+
+typedef struct _GtkSearchEngineBeagle GtkSearchEngineBeagle;
+typedef struct _GtkSearchEngineBeagleClass GtkSearchEngineBeagleClass;
+typedef struct _GtkSearchEngineBeaglePrivate GtkSearchEngineBeaglePrivate;
+
+struct _GtkSearchEngineBeagle 
+{
+  GtkSearchEngine parent;
+
+  GtkSearchEngineBeaglePrivate *priv;
+};
+
+struct _GtkSearchEngineBeagleClass 
+{
+  GtkSearchEngineClass parent_class;
+};
+
+GType            _gtk_search_engine_beagle_get_type (void);
+
+GtkSearchEngine* _gtk_search_engine_beagle_new      (void);
+
+G_END_DECLS
+
+#endif /* __GTK_SEARCH_ENGINE_BEAGLE_H__ */
diff --git a/gtk/gtksearchenginesimple.c b/gtk/gtksearchenginesimple.c
new file mode 100644 (file)
index 0000000..bfe1aec
--- /dev/null
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2005 Red Hat, Inc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ *
+ * Based on nautilus-search-engine-simple.c
+ */
+
+#define _XOPEN_SOURCE 500
+#define _GNU_SOURCE 
+
+#include <config.h>
+#include "gtksearchenginesimple.h"
+
+#define XDG_PREFIX _gtk_xdg
+#include "xdgmime/xdgmime.h"
+
+#include <string.h>
+#include <ftw.h>
+#include <glib/gstrfuncs.h>
+
+#define BATCH_SIZE 500
+
+typedef struct 
+{
+  GtkSearchEngineSimple *engine;
+  
+  gchar *path;
+  GList *mime_types;
+  gchar **words;
+  GList *found_list;
+  
+  gint n_processed_files;
+  GList *uri_hits;
+  
+  /* accessed on both threads: */
+  volatile gboolean cancelled;
+} SearchThreadData;
+
+
+struct _GtkSearchEngineSimplePrivate 
+{
+  GtkQuery *query;
+  
+  SearchThreadData *active_search;
+  
+  gboolean query_finished;
+};
+
+
+G_DEFINE_TYPE (GtkSearchEngineSimple, _gtk_search_engine_simple, GTK_TYPE_SEARCH_ENGINE);
+
+static void
+finalize (GObject *object)
+{
+  GtkSearchEngineSimple *simple;
+  
+  simple = GTK_SEARCH_ENGINE_SIMPLE (object);
+  
+  if (simple->priv->query) 
+    {
+      g_object_unref (simple->priv->query);
+      simple->priv->query = NULL;
+    }
+  
+  g_free (simple->priv);
+  
+  G_OBJECT_CLASS (_gtk_search_engine_simple_parent_class)->finalize (object);
+}
+
+static SearchThreadData *
+search_thread_data_new (GtkSearchEngineSimple *engine,
+                       GtkQuery              *query)
+{
+  SearchThreadData *data;
+  char *text, *lower, *uri;
+  
+  data = g_new0 (SearchThreadData, 1);
+  
+  data->engine = engine;
+  uri = _gtk_query_get_location (query);
+  if (uri != NULL) 
+    {
+      data->path = g_filename_from_uri (uri, NULL, NULL);
+      g_free (uri);
+    }
+  if (data->path == NULL)
+    data->path = g_strdup (g_get_home_dir ());
+       
+  text = _gtk_query_get_text (query);
+  lower = g_ascii_strdown (text, -1);
+  data->words = g_strsplit (lower, " ", -1);
+  g_free (text);
+  g_free (lower);
+  
+  data->mime_types = _gtk_query_get_mime_types (query);
+  
+  return data;
+}
+
+static void 
+search_thread_data_free (SearchThreadData *data)
+{
+  g_free (data->path);
+  g_strfreev (data->words);
+  g_list_foreach (data->mime_types, (GFunc)g_free, NULL);
+  g_list_free (data->mime_types);
+  g_free (data);
+}
+
+static gboolean
+search_thread_done_idle (gpointer user_data)
+{
+  SearchThreadData *data;
+
+  data = user_data;
+  
+  if (!data->cancelled) 
+    {
+      _gtk_search_engine_finished (GTK_SEARCH_ENGINE (data->engine));
+      data->engine->priv->active_search = NULL;
+    }
+  
+  search_thread_data_free (data);
+  
+  return FALSE;
+}
+
+typedef struct 
+{
+  GList *uris;
+  SearchThreadData *thread_data;
+} SearchHits;
+
+
+static gboolean
+search_thread_add_hits_idle (gpointer user_data)
+{
+  SearchHits *hits;
+
+  hits = user_data;
+
+  if (!hits->thread_data->cancelled) 
+    {
+      _gtk_search_engine_hits_added (GTK_SEARCH_ENGINE (hits->thread_data->engine),
+                                   hits->uris);
+    }
+
+  g_list_foreach (hits->uris, (GFunc)g_free, NULL);
+  g_list_free (hits->uris);
+  g_free (hits);
+       
+  return FALSE;
+}
+
+static void
+send_batch (SearchThreadData *data)
+{
+  SearchHits *hits;
+  
+  data->n_processed_files = 0;
+  
+  if (data->uri_hits) 
+    {
+      hits = g_new (SearchHits, 1);
+      hits->uris = data->uri_hits;
+      hits->thread_data = data;
+      g_idle_add (search_thread_add_hits_idle, hits);
+    }
+  data->uri_hits = NULL;
+}
+
+static GStaticPrivate search_thread_data = G_STATIC_PRIVATE_INIT;
+
+static int
+search_visit_func (const char        *fpath,
+                  const struct stat *sb,
+                  int                typeflag,
+                  struct FTW        *ftwbuf)
+{
+  SearchThreadData *data;
+  gint i;
+  const gchar *name; 
+  gchar *lower_name, *path, *mime_type;
+  gchar *uri;
+  gboolean hit;
+  GList *l;
+  gboolean is_hidden;
+  
+  data = (SearchThreadData*)g_static_private_get (&search_thread_data);
+  
+  if (data->cancelled)
+    return FTW_STOP;
+
+  name = strrchr (fpath, '/');
+  if (name)
+    name++;
+  else
+    name = fpath;
+
+  path = g_build_filename (data->path, fpath, NULL);
+  
+  is_hidden = *name == '.';
+       
+  hit = FALSE;
+  
+  if (!is_hidden) 
+    {
+      lower_name = g_ascii_strdown (name, -1);
+      
+      hit = TRUE;
+      for (i = 0; data->words[i] != NULL; i++) 
+       {
+         if (strstr (lower_name, data->words[i]) == NULL) 
+           {
+             hit = FALSE;
+             break;
+           }
+       }
+      g_free (lower_name);
+    }
+
+  if (hit && data->mime_types != NULL) 
+    {
+      hit = FALSE;
+      mime_type = xdg_mime_get_mime_type_for_file (path, (struct stat *)sb);
+      for (l = data->mime_types; l != NULL; l = l->next) 
+       {
+         if (strcmp (mime_type, l->data) == 0) 
+           {
+             hit = TRUE;
+             break;
+           }
+       }
+
+      g_free (mime_type);
+    }
+
+  if (hit) 
+    {
+      uri = g_filename_to_uri (path, NULL, NULL);
+      data->uri_hits = g_list_prepend (data->uri_hits, uri);
+    }
+
+  data->n_processed_files++;
+  
+  if (data->n_processed_files > BATCH_SIZE)
+    send_batch (data);
+       
+  if (is_hidden)
+    return FTW_SKIP_SUBTREE;
+  else
+    return FTW_CONTINUE;
+}
+
+static gpointer 
+search_thread_func (gpointer user_data)
+{
+  SearchThreadData *data;
+  
+  data = user_data;
+  
+  g_static_private_set (&search_thread_data, data, NULL);
+
+  nftw (data->path, search_visit_func, 20, FTW_ACTIONRETVAL | FTW_PHYS);
+
+  send_batch (data);
+  
+  g_idle_add (search_thread_done_idle, data);
+  
+  return NULL;
+}
+
+static void
+gtk_search_engine_simple_start (GtkSearchEngine *engine)
+{
+  GtkSearchEngineSimple *simple;
+  SearchThreadData *data;
+  
+  simple = GTK_SEARCH_ENGINE_SIMPLE (engine);
+  
+  if (simple->priv->active_search != NULL)
+    return;
+  
+  if (simple->priv->query == NULL)
+    return;
+       
+  data = search_thread_data_new (simple, simple->priv->query);
+  
+  g_thread_create (search_thread_func, data, FALSE, NULL);
+  
+  simple->priv->active_search = data;
+}
+
+static void
+gtk_search_engine_simple_stop (GtkSearchEngine *engine)
+{
+  GtkSearchEngineSimple *simple;
+  
+  simple = GTK_SEARCH_ENGINE_SIMPLE (engine);
+  
+  if (simple->priv->active_search != NULL) 
+    {
+      simple->priv->active_search->cancelled = TRUE;
+      simple->priv->active_search = NULL;
+    }
+}
+
+static gboolean
+gtk_search_engine_simple_is_indexed (GtkSearchEngine *engine)
+{
+  return FALSE;
+}
+
+static void
+gtk_search_engine_simple_set_query (GtkSearchEngine *engine, 
+                                   GtkQuery        *query)
+{
+  GtkSearchEngineSimple *simple;
+  
+  simple = GTK_SEARCH_ENGINE_SIMPLE (engine);
+  
+  if (query)
+    g_object_ref (query);
+
+  if (simple->priv->query) 
+    g_object_unref (simple->priv->query);
+
+  simple->priv->query = query;
+}
+
+static void
+_gtk_search_engine_simple_class_init (GtkSearchEngineSimpleClass *class)
+{
+  GObjectClass *gobject_class;
+  GtkSearchEngineClass *engine_class;
+  
+  gobject_class = G_OBJECT_CLASS (class);
+  gobject_class->finalize = finalize;
+  
+  engine_class = GTK_SEARCH_ENGINE_CLASS (class);
+  engine_class->set_query = gtk_search_engine_simple_set_query;
+  engine_class->start = gtk_search_engine_simple_start;
+  engine_class->stop = gtk_search_engine_simple_stop;
+  engine_class->is_indexed = gtk_search_engine_simple_is_indexed;
+
+  g_type_class_add_private (gobject_class, sizeof (GtkSearchEngineSimplePrivate));
+}
+
+static void
+_gtk_search_engine_simple_init (GtkSearchEngineSimple *engine)
+{
+  engine->priv = G_TYPE_INSTANCE_GET_PRIVATE (engine, GTK_TYPE_SEARCH_ENGINE_SIMPLE, GtkSearchEngineSimplePrivate);
+}
+
+GtkSearchEngine *
+_gtk_search_engine_simple_new (void)
+{
+  GtkSearchEngine *engine;
+  
+  engine = g_object_new (GTK_TYPE_SEARCH_ENGINE_SIMPLE, NULL);
+  
+  return engine;
+}
diff --git a/gtk/gtksearchenginesimple.h b/gtk/gtksearchenginesimple.h
new file mode 100644 (file)
index 0000000..1a93000
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2005 Red Hat, Inc
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *
+ * Author: Alexander Larsson <alexl@redhat.com>
+ *
+ * Based on nautilus-search-engine-simple.h
+ */
+
+#ifndef __GTK_SEARCH_ENGINE_SIMPLE_H__
+#define __GTK_SEARCH_ENGINE_SIMPLE_H__
+
+#include "gtksearchengine.h"
+
+G_END_DECLS
+
+#define GTK_TYPE_SEARCH_ENGINE_SIMPLE          (_gtk_search_engine_simple_get_type ())
+#define GTK_SEARCH_ENGINE_SIMPLE(obj)          (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SEARCH_ENGINE_SIMPLE, GtkSearchEngineSimple))
+#define GTK_SEARCH_ENGINE_SIMPLE_CLASS(klass)  (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SEARCH_ENGINE_SIMPLE, GtkSearchEngineSimpleClass))
+#define GTK_IS_SEARCH_ENGINE_SIMPLE(obj)               (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SEARCH_ENGINE_SIMPLE))
+#define GTK_IS_SEARCH_ENGINE_SIMPLE_CLASS(klass)       (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SEARCH_ENGINE_SIMPLE))
+#define GTK_SEARCH_ENGINE_SIMPLE_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SEARCH_ENGINE_SIMPLE, GtkSearchEngineSimpleClass))
+
+typedef struct _GtkSearchEngineSimple GtkSearchEngineSimple;
+typedef struct _GtkSearchEngineSimpleClass GtkSearchEngineSimpleClass;
+typedef struct _GtkSearchEngineSimplePrivate GtkSearchEngineSimplePrivate;
+
+struct _GtkSearchEngineSimple 
+{
+  GtkSearchEngine parent;
+
+  GtkSearchEngineSimplePrivate *priv;
+};
+
+struct _GtkSearchEngineSimpleClass
+{
+  GtkSearchEngineClass parent_class;
+};
+
+GType            _gtk_search_engine_simple_get_type (void);
+
+GtkSearchEngine* _gtk_search_engine_simple_new      (void);
+
+G_END_DECLS
+
+#endif /* __GTK_SEARCH_ENGINE_SIMPLE_H__ */
diff --git a/gtk/gtksearchenginetracker.c b/gtk/gtksearchenginetracker.c
new file mode 100644 (file)
index 0000000..7d63461
--- /dev/null
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2005 Mr Jamie McCracken
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *
+ * Author: Jamie McCracken <jamiemcc@gnome.org>
+ *
+ * Based on nautilus-search-engine-tracker.c
+ */
+
+#include <config.h>
+#include <gmodule.h>
+#include "gtksearchenginetracker.h"
+#if 0
+#include <tracker.h>
+#endif
+
+typedef struct _TrackerClient TrackerClient;
+
+typedef void (*TrackerArrayReply) (char **result, GError *error, gpointer user_data);
+
+static TrackerClient * (*tracker_connect) (gboolean enable_warnings) = NULL;
+static void           (*tracker_disconnect) (TrackerClient *client) = NULL;
+static void            (*tracker_cancel_last_call) (TrackerClient *client) = NULL;
+
+static void (*tracker_search_metadata_by_text_async) (TrackerClient *client, 
+                                                     const char *query, 
+                                                     TrackerArrayReply callback, 
+                                                     gpointer user_data) = NULL;
+static void (*tracker_search_metadata_by_text_and_mime_async) (TrackerClient *client, 
+                                                              const char *query, 
+                                                              const char **mimes, 
+                                                              TrackerArrayReply callback, 
+                                                              gpointer user_data) = NULL;
+static void (*tracker_search_metadata_by_text_and_location_async) (TrackerClient *client, 
+                                                                  const char *query, 
+                                                                  const char *location, 
+                                                                  TrackerArrayReply callback, 
+                                                                  gpointer user_data) = NULL;
+static void (*tracker_search_metadata_by_text_and_mime_and_location_async) (TrackerClient *client, 
+                                                                           const char *query, 
+                                                                           const char **mimes, 
+                                                                           const char *location, 
+                                                                           TrackerArrayReply callback, 
+                                                                           gpointer user_data) = NULL;
+
+static struct TrackerDlMapping
+{
+  const char *fn_name;
+  gpointer *fn_ptr_ref;
+} tracker_dl_mapping[] =
+{
+#define MAP(a) { #a, (gpointer *)&a }
+  MAP (tracker_connect),
+  MAP (tracker_disconnect),
+  MAP (tracker_cancel_last_call),
+  MAP (tracker_search_metadata_by_text_async),
+  MAP (tracker_search_metadata_by_text_and_mime_async),
+  MAP (tracker_search_metadata_by_text_and_location_async),
+  MAP (tracker_search_metadata_by_text_and_mime_and_location_async)
+#undef MAP
+};
+
+static void 
+open_libtracker (void)
+{
+  static gboolean done = FALSE;
+
+  if (!done)
+    {
+      int i;
+      GModule *tracker;
+      
+      done = TRUE;
+      tracker = g_module_open ("libtracker.so.0", G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL);
+      if (!tracker)
+       return;
+      
+      for (i = 0; i < G_N_ELEMENTS (tracker_dl_mapping); i++)
+       {
+         if (!g_module_symbol (tracker, tracker_dl_mapping[i].fn_name,
+                               tracker_dl_mapping[i].fn_ptr_ref))
+           {
+             g_warning ("Missing symbol '%s' in libtracker\n",
+                        tracker_dl_mapping[i].fn_name);
+             g_module_close (tracker);
+
+             for (i = 0; i < G_N_ELEMENTS (tracker_dl_mapping); i++)
+               tracker_dl_mapping[i].fn_ptr_ref = NULL;
+
+             return;
+           }
+       }
+    }
+}
+
+struct _GtkSearchEngineTrackerPrivate 
+{
+  GtkQuery     *query;
+  TrackerClient *client;
+  gboolean      query_pending;
+};
+
+G_DEFINE_TYPE (GtkSearchEngineTracker, _gtk_search_engine_tracker, GTK_TYPE_SEARCH_ENGINE);
+
+
+static void
+finalize (GObject *object)
+{
+  GtkSearchEngineTracker *tracker;
+  
+  tracker = GTK_SEARCH_ENGINE_TRACKER (object);
+  
+  if (tracker->priv->query) 
+    {
+      g_object_unref (tracker->priv->query);
+      tracker->priv->query = NULL;
+    }
+
+  tracker_disconnect (tracker->priv->client);
+
+  G_OBJECT_CLASS (_gtk_search_engine_tracker_parent_class)->finalize (object);
+}
+
+
+static void
+search_callback (gchar  **results, 
+                GError  *error, 
+                gpointer user_data)
+{
+  GtkSearchEngineTracker *tracker;
+  gchar **results_p;
+  GList *hit_uris;
+  
+  tracker = GTK_SEARCH_ENGINE_TRACKER (user_data);
+  hit_uris = NULL;
+  
+  tracker->priv->query_pending = FALSE;
+
+  if (error) 
+    {
+      _gtk_search_engine_error ( GTK_SEARCH_ENGINE (tracker), error->message);
+      g_error_free (error);
+      return;
+    }
+
+  if (!results)
+    return;
+       
+  for (results_p = results; *results_p; results_p++) 
+    {
+      gchar *uri;
+
+      uri = g_filename_to_uri ((char *)*results_p, NULL, NULL);
+      if (uri)
+       hit_uris = g_list_prepend (hit_uris, (char *)uri);
+    }
+
+  _gtk_search_engine_hits_added (GTK_SEARCH_ENGINE (tracker), hit_uris);
+  _gtk_search_engine_finished (GTK_SEARCH_ENGINE (tracker));
+
+  g_strfreev  (results);
+  g_list_foreach (hit_uris, (GFunc)g_free, NULL);
+  g_list_free (hit_uris);
+}
+
+
+static void
+gtk_search_engine_tracker_start (GtkSearchEngine *engine)
+{
+  GtkSearchEngineTracker *tracker;
+  GList        *mimetypes, *l;
+  gchar        *search_text, *location, *location_uri;
+  gchar        **mimes;
+  gint         i, mime_count;
+
+  tracker = GTK_SEARCH_ENGINE_TRACKER (engine);
+
+  if (tracker->priv->query_pending)
+    return;
+
+  if (tracker->priv->query == NULL)
+    return;
+       
+  search_text = _gtk_query_get_text (tracker->priv->query);
+  
+  mimetypes = _gtk_query_get_mime_types (tracker->priv->query);
+  
+  location_uri = _gtk_query_get_location (tracker->priv->query);
+  
+  if (location_uri)
+    {
+      location = g_filename_from_uri (location_uri, NULL, NULL);
+      g_free (location_uri);
+    } 
+  else 
+    {
+      location = NULL;
+    }
+
+  mime_count  = g_list_length (mimetypes);
+
+  i = 0;
+
+  /* convert list into array */
+  if (mime_count > 0) 
+    {
+      mimes = g_new (gchar *, (mime_count + 1));
+      
+      for (l = mimetypes; l != NULL; l = l->next) 
+       {
+         mimes[i] = g_strdup (l->data);
+         i++;
+       }
+
+      mimes[mime_count] = NULL;                        
+      
+      if (location) 
+       {
+         tracker_search_metadata_by_text_and_mime_and_location_async (tracker->priv->client,
+                                                                      search_text, (const char **)mimes, location,
+                                                                      search_callback, 
+                                                                      tracker);
+         g_free (location);
+       } 
+      else 
+       {
+         tracker_search_metadata_by_text_and_mime_async (tracker->priv->client, 
+                                                         search_text, (const char**)mimes, 
+                                                         search_callback,
+                                                         tracker);
+       }
+      
+      g_strfreev (mimes);
+               
+      
+    }
+  else 
+    {
+      if (location) 
+       {
+         tracker_search_metadata_by_text_and_location_async (tracker->priv->client,
+                                                             search_text, 
+                                                             location, 
+                                                             search_callback,
+                                                             tracker);
+         g_free (location);
+       } 
+      else 
+       {
+         tracker_search_metadata_by_text_async (tracker->priv->client, 
+                                                search_text, 
+                                                search_callback,
+                                                tracker);
+       }
+    }
+
+  tracker->priv->query_pending = TRUE;
+  g_free (search_text);
+  g_list_foreach (mimetypes, (GFunc)g_free, NULL);
+  g_list_free (mimetypes);
+}
+
+static void
+gtk_search_engine_tracker_stop (GtkSearchEngine *engine)
+{
+  GtkSearchEngineTracker *tracker;
+  
+  tracker = GTK_SEARCH_ENGINE_TRACKER (engine);
+  
+  if (tracker->priv->query && tracker->priv->query_pending) 
+    {
+      tracker_cancel_last_call (tracker->priv->client);
+      tracker->priv->query_pending = FALSE;
+    }
+}
+
+static gboolean
+gtk_search_engine_tracker_is_indexed (GtkSearchEngine *engine)
+{
+  return TRUE;
+}
+
+static void
+gtk_search_engine_tracker_set_query (GtkSearchEngine *engine, 
+                                    GtkQuery        *query)
+{
+  GtkSearchEngineTracker *tracker;
+  
+  tracker = GTK_SEARCH_ENGINE_TRACKER (engine);
+  
+  if (query) 
+    g_object_ref (query);
+
+  if (tracker->priv->query)
+    g_object_unref (tracker->priv->query);
+
+  tracker->priv->query = query;
+}
+
+static void
+_gtk_search_engine_tracker_class_init (GtkSearchEngineTrackerClass *class)
+{
+  GObjectClass *gobject_class;
+  GtkSearchEngineClass *engine_class;
+  
+  gobject_class = G_OBJECT_CLASS (class);
+  gobject_class->finalize = finalize;
+  
+  engine_class = GTK_SEARCH_ENGINE_CLASS (class);
+  engine_class->set_query = gtk_search_engine_tracker_set_query;
+  engine_class->start = gtk_search_engine_tracker_start;
+  engine_class->stop = gtk_search_engine_tracker_stop;
+  engine_class->is_indexed = gtk_search_engine_tracker_is_indexed;
+
+  g_type_class_add_private (gobject_class, sizeof (GtkSearchEngineTrackerPrivate));
+}
+
+static void
+_gtk_search_engine_tracker_init (GtkSearchEngineTracker *engine)
+{
+  engine->priv = G_TYPE_INSTANCE_GET_PRIVATE (engine, GTK_TYPE_SEARCH_ENGINE_TRACKER, GtkSearchEngineTrackerPrivate);
+}
+
+
+GtkSearchEngine *
+_gtk_search_engine_tracker_new (void)
+{
+  GtkSearchEngineTracker *engine;
+  TrackerClient *tracker_client;
+
+  open_libtracker ();
+
+  if (!tracker_connect)
+    return NULL;
+
+  tracker_client = tracker_connect (FALSE);
+  
+  if (!tracker_client)
+    return NULL;
+
+  engine = g_object_new (GTK_TYPE_SEARCH_ENGINE_TRACKER, NULL);
+
+  engine->priv->client = tracker_client;
+
+  engine->priv->query_pending = FALSE;
+  
+  return GTK_SEARCH_ENGINE (engine);
+}
diff --git a/gtk/gtksearchenginetracker.h b/gtk/gtksearchenginetracker.h
new file mode 100644 (file)
index 0000000..089b7e8
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2005 Mr Jamie McCracken
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *
+ * Author: Jamie McCracken (jamiemcc@gnome.org)
+ *
+ * Based on nautilus-search-engine-tracker.h
+ */
+
+#ifndef __GTK_SEARCH_ENGINE_TRACKER_H__
+#define __GTK_SEARCH_ENGINE_TRACKER_H__
+
+#include "gtksearchengine.h"
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_SEARCH_ENGINE_TRACKER         (_gtk_search_engine_tracker_get_type ())
+#define GTK_SEARCH_ENGINE_TRACKER(obj)         (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_SEARCH_ENGINE_TRACKER, GtkSearchEngineTracker))
+#define GTK_SEARCH_ENGINE_TRACKER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_SEARCH_ENGINE_TRACKER, GtkSearchEngineTrackerClass))
+#define GTK_IS_SEARCH_ENGINE_TRACKER(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_SEARCH_ENGINE_TRACKER))
+#define GTK_IS_SEARCH_ENGINE_TRACKER_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_SEARCH_ENGINE_TRACKER))
+#define GTK_SEARCH_ENGINE_TRACKER_GET_CLASS(obj)   (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_SEARCH_ENGINE_TRACKER, GtkSearchEngineTrackerClass))
+
+typedef struct _GtkSearchEngineTracker GtkSearchEngineTracker;
+typedef struct _GtkSearchEngineTrackerClass GtkSearchEngineTrackerClass;
+typedef struct _GtkSearchEngineTrackerPrivate GtkSearchEngineTrackerPrivate;
+
+struct _GtkSearchEngineTracker 
+{
+  GtkSearchEngine parent;
+
+  GtkSearchEngineTrackerPrivate *priv;
+};
+
+struct _GtkSearchEngineTrackerClass 
+{
+  GtkSearchEngineClass parent_class;
+};
+
+GType            _gtk_search_engine_tracker_get_type (void);
+
+GtkSearchEngine* _gtk_search_engine_tracker_new      (void);
+
+G_END_DECLS
+
+#endif /* __GTK_SEARCH_ENGINE_TRACKER_H__ */